<script setup lang="ts">
import OptionBlock from '~/components/aigc/drawing/OptionBlock.vue';
import ResultBlock from '~/components/aigc/drawing/ResultBlock.vue';
import {useLoginState} from '~/composables/useLoginState';
import ModalAuthentication from '~/components/ModalAuthentication.vue';
import {type InferType, number, object, string} from 'yup';
import type {FormSubmitEvent} from '#ui/types';
import RatioSelector from '~/components/aigc/RatioSelector.vue';
import {useFetchWrapped} from '~/composables/useFetchWrapped';
import type {ResultBlockMeta} from '~/components/aigc/drawing';
import {useHistory} from '~/composables/useHistory';
import {del, set} from 'idb-keyval';
import ReferenceFigureSelector from '~/components/aigc/ReferenceFigureSelector.vue';

useHead({
  title: '绘画 | XSH AI',
})

const toast = useToast()
const modal = useModal()
const dayjs = useDayjs()
const history = useHistory()
const loginState = useLoginState()

const leftSection = ref<HTMLElement | null>(null)
const leftHandler = ref<HTMLElement | null>(null)
const showSidebar = ref(false)

const generating = ref(false)

const handle_stick_mousedown = (e: MouseEvent, min: number = 240, max: number = 400) => {
  const handler = leftHandler.value
  if (handler) {
    const startX = e.clientX
    const startWidth = handler.parentElement?.offsetWidth || 0
    const handle_mousemove = (e: MouseEvent) => {
      let newWidth = startWidth + e.clientX - startX
      if (newWidth < min || newWidth > max) {
        newWidth = Math.min(Math.max(newWidth, min), max)
      }
      handler.parentElement!.style.width = `${newWidth}px`
    }
    const handle_mouseup = () => {
      leftSection.value?.classList.add('transition-all')
      leftHandler.value?.lastElementChild?.classList.remove('bg-indigo-300', 'dark:bg-indigo-700', 'w-[3px]')
      window.removeEventListener('mousemove', handle_mousemove)
      window.removeEventListener('mouseup', handle_mouseup)
    }
    leftSection.value?.classList.remove('transition-all')
    leftHandler.value?.lastElementChild?.classList.add('bg-indigo-300', 'dark:bg-indigo-700', 'w-[3px]')
    window.addEventListener('mousemove', handle_mousemove)
    window.addEventListener('mouseup', handle_mouseup)
  }
}

const defaultRatios = [
  {
    ratio: '1:1',
    value: '768:768',
  },
  {
    ratio: '4:3',
    value: '1024:768',
  },
  {
    ratio: '3:4',
    value: '768:1024',
  },
]

interface StyleItem {
  label: string
  value: number
  avatar?: { src: string }
}

const defaultStyles: StyleItem[] = [
  {
    label: '通用写实风格',
    value: 401,
  },
  {
    label: '日系动漫',
    value: 201,
  },
  {
    label: '科幻风格',
    value: 114,
  },
  {
    label: '怪兽风格',
    value: 202,
  },
  {
    label: '唯美古风',
    value: 203,
  },
  {
    label: '复古动漫',
    value: 204,
  },
  {
    label: '游戏卡通手绘',
    value: 301,
  },
  {
    label: '水墨画',
    value: 101,
  },
  {
    label: '概念艺术',
    value: 102,
  },
  {
    label: '水彩画',
    value: 104,
  },
  {
    label: '像素画',
    value: 105,
  },
  {
    label: '厚涂风格',
    value: 106,
  },
  {
    label: '插图',
    value: 107,
  },
  {
    label: '剪纸风格',
    value: 108,
  },
  {
    label: '印象派',
    value: 119,
  },
  {
    label: '印象派（莫奈）',
    value: 109,
  },
  {
    label: '油画',
    value: 103,
  },
  {
    label: '油画（梵高）',
    value: 118,
  },
  {
    label: '古典肖像画',
    value: 111,
  },
  {
    label: '黑白素描画',
    value: 112,
  },
  {
    label: '赛博朋克',
    value: 113,
  },
  {
    label: '暗黑风格',
    value: 115,
  },
  {
    label: '蒸汽波',
    value: 117,
  },
  {
    label: '2.5D',
    value: 110,
  },
  {
    label: '3D',
    value: 116,
  },
]
const img2imgStyles: StyleItem[] = [
  {
    label: '水彩画',
    value: 106,
  },
  {
    label: '2.5D',
    value: 110,
  },
  {
    label: '日系动漫',
    value: 201,
  },
  {
    label: '美系动漫',
    value: 202,
  },
  {
    label: '唯美古风',
    value: 203,
  },
]

const defaultFormSchema = object({
  prompt: string().required('请输入提示词'),
  negative_prompt: string(),
  resolution: string().required('请选择分辨率'),
  styles: object<StyleItem>({
    label: string(),
    value: number(),
  }).required('请选择风格'),
  file: string().nullable(),
})

type DefaultFormSchema = InferType<typeof defaultFormSchema>

const defaultFormState = reactive({
  prompt: '',
  negative_prompt: '',
  resolution: '1024:768',
  styles: defaultStyles.find(item => item.value === 401),
  file: null,
})
watch(() => defaultFormState.file, (newVal) => {
  if (newVal) {
    defaultFormState.styles = img2imgStyles[0]
  } else {
    defaultFormState.styles = defaultStyles.find(item => item.value === 401)
  }
})

const onDefaultFormSubmit = (event: FormSubmitEvent<DefaultFormSchema>) => {
  if (!loginState.is_logged_in) {
    modal.open(ModalAuthentication)
    return
  }
  generating.value = true
  const styleItem = event.data.styles as StyleItem
  if (!event.data.file) delete event.data.file
  // generate a uuid
  const fid = Math.random().toString(36).substring(2)
  const meta: ResultBlockMeta = {
    cost: '1000',
    modal: '混元大模型',
    style: styleItem.label,
    ratio: event.data.resolution,
    datetime: dayjs().unix(),
    type: event.data.file ? '智能图生图' : '智能文生图',
  }
  history.text2img.unshift({
    fid,
    meta,
    prompt: event.data.prompt,
  })
  useFetchWrapped<
    (HunYuan.Text2Img.req | HunYuan.Img2Img.req) & AuthedRequest,
    BaseResponse<HunYuan.resp>
  >(event.data.file ? 'App.Assistant_HunYuan.TenImgToImg' : 'App.Assistant_HunYuan.TenTextToImg', {
    token: loginState.token as string,
    user_id: loginState.user.id,
    device_id: 'web',
    ...event.data,
    styles: styleItem.value,
  }).then(res => {
    if (res.ret !== 200) {
      toast.add({
        title: '生成失败',
        description: res.msg || '未知错误',
        color: 'red',
        icon: 'i-tabler-circle-x',
      })
      history.text2img = history.text2img.filter(item => item.fid !== fid)
      return
    }
    history.text2img = history.text2img.map(item => {
      if (item.fid === fid) {
        set(`${item.fid}`, [`data:image/png;base64,${res.data.request_image}`])
        item.meta = {
          ...item.meta,
          id: res.data.data_id as string,
        }
      }
      return item
    })
  }).catch(err => {
    toast.add({
      title: '生成失败',
      description: err.msg || '网络错误',
      color: 'red',
      icon: 'i-tabler-circle-x',
    })
  }).finally(() => {
    generating.value = false
  })
}
</script>

<template>
  <div class="w-full flex relative">
    <div ref="leftSection"
         class="absolute -translate-x-full md:sticky md:translate-x-0 z-10 md:block h-[calc(100vh-4rem)] bg-neutral-200 dark:bg-neutral-800 transition-all"
         style="width: 320px" :class="{'translate-x-0': showSidebar}">
      <div ref="leftHandler"
           class="absolute inset-0 left-auto hidden xl:flex flex-col justify-center items-center cursor-ew-resize px-1 group"
           @dblclick="leftSection?.style.setProperty('width', '320px')"
           @mousedown.prevent="handle_stick_mousedown">
        <span
          class="w-[1px] h-full bg-neutral-300 dark:bg-neutral-700 group-hover:bg-indigo-300 dark:group-hover:bg-indigo-700 group-hover:w-[3px] transition-all group-hover:delay-500 translate-x-1"></span>
      </div>
      <div
        class="absolute bottom-28 -right-12 w-12 h-12 z-10 bg-neutral-100 dark:bg-neutral-800 rounded-r-lg shadow-lg flex md:hidden justify-center items-center">
        <UButton color="black" icon="i-tabler-brush" size="lg" square @click="showSidebar = !showSidebar"></UButton>
      </div>
      <div class="h-full flex flex-col overflow-y-auto">
        <UForm :schema="defaultFormSchema" :state="defaultFormState" @submit="onDefaultFormSubmit">
          <div class="flex flex-col gap-2 p-4 pb-28">
            <OptionBlock comment="Prompts" icon="i-tabler-article" label="提示词">
              <UFormGroup name="prompt">
                <UTextarea v-model="defaultFormState.prompt" :rows="2" autoresize
                           placeholder="请输入英文提示词，每个提示词之间用英文逗号隔开" resize/>
              </UFormGroup>
            </OptionBlock>
            <OptionBlock comment="Negative Prompts" icon="i-tabler-article-off" label="负面提示词">
              <UFormGroup name="negative_prompt">
                <UTextarea v-model="defaultFormState.negative_prompt" :rows="2" autoresize
                           placeholder="请输入作品中不要出现的提示词，每个提示词之间用英文逗号隔开"
                           resize/>
              </UFormGroup>
            </OptionBlock>
            <OptionBlock icon="i-tabler-library-photo" label="参考图片">
              <UFormGroup name="input_image">
                <ReferenceFigureSelector
                  :value="defaultFormState.file"
                  @update="file => {defaultFormState.file = file; console.log('recv', file)}"
                  text="选择参考图片" text-on-select="已选择参考图"/>
              </UFormGroup>
            </OptionBlock>
            <OptionBlock icon="i-tabler-photo-hexagon" label="图片风格">
              <UFormGroup name="styles">
                <USelectMenu v-model="defaultFormState.styles"
                             :options="defaultFormState.file ? img2imgStyles : defaultStyles"></USelectMenu>
              </UFormGroup>
            </OptionBlock>
            <OptionBlock icon="i-tabler-article-off" label="图片比例">
              <UFormGroup name="resolution">
                <RatioSelector v-model="defaultFormState.resolution" :ratios="defaultRatios"/>
              </UFormGroup>
            </OptionBlock>
          </div>
          <div class="absolute bottom-0 inset-x-0 flex flex-col items-center gap-2
                      bg-neutral-200 dark:bg-neutral-800 p-4 border-t border-neutral-400
                      dark:border-neutral-700">
            <UButton type="submit" color="indigo" size="lg" class="font-bold" :loading="generating" block>
              {{ generating ? '生成中' : '生成' }}
            </UButton>
            <p class="text-xs text-neutral-400 dark:text-neutral-500 font-bold">
              生成即代表您同意<a href="#" target="_blank"
                                 class="underline underline-offset-2">用户许可协议</a>
            </p>
          </div>
        </UForm>
      </div>
    </div>
    <ClientOnly>
      <div class="flex-1 h-screen flex flex-col gap-4 bg-neutral-100 dark:bg-neutral-900 p-4 pb-20 overflow-y-auto">
        <div v-if="!loginState.is_logged_in"
             class="w-full h-full flex flex-col justify-center items-center gap-2 bg-neutral-100 dark:bg-neutral-900">
          <Icon name="i-tabler-user-circle" class="text-7xl text-neutral-300 dark:text-neutral-700"/>
          <p class="text-sm text-neutral-500 dark:text-neutral-400">请登录后使用</p>
          <UButton class="mt-2 font-bold" @click="modal.open(ModalAuthentication)" color="black" variant="solid"
                   size="xs">
            登录
          </UButton>
        </div>
        <div v-else-if="history.text2img.length === 0"
             class="w-full h-full flex flex-col justify-center items-center gap-2 bg-neutral-100 dark:bg-neutral-900">
          <Icon name="i-tabler-photo-hexagon" class="text-7xl text-neutral-300 dark:text-neutral-700"/>
          <p class="text-sm text-neutral-500 dark:text-neutral-400">没有记录</p>
        </div>
        <ResultBlock v-else v-for="(result, k) in history.text2img" :fid="result.fid" :meta="result.meta"
                     :prompt="result.prompt" :key="result.fid"
                     @use-reference="file => {defaultFormState.file = file}">
          <template #header-right>
            <UPopover overlay>
              <UButton color="black" size="xs" icon="i-tabler-trash" variant="ghost"></UButton>
              <template #panel="{close}">
                <div class="p-4 flex flex-col gap-4">
                  <h2 class="text-sm">删除后无法恢复，确定删除？</h2>
                  <div class="flex items-center justify-end gap-2">
                    <UButton color="gray" size="xs" class="font-bold" @click="close">
                      取消
                    </UButton>
                    <UButton color="red" size="xs" class="font-bold"
                             @click="() => {
                               history.text2img.splice(k, 1)
                               del(result.fid)
                               close()
                             }">
                      仍然删除
                    </UButton>
                  </div>
                </div>
              </template>
            </UPopover>
          </template>
        </ResultBlock>
        <div class="flex justify-center items-center gap-1 text-neutral-400 dark:text-neutral-600">
          <UIcon name="i-tabler-info-triangle"/>
          <p class="text-xs font-bold">所有图片均为 AI 生成，服务器不会保存任何图像，数据仅保存在浏览器本地</p>
        </div>
      </div>
    </ClientOnly>
  </div>
</template>

<style scoped>

</style>