// @unocss-include
import { FactorObject, objectId, vue } from '@factor/api'
import type { TableCardConfig, TableSiteConfig } from './tables'
import { themes } from './themes'
import { Card, type CardTemplate } from './card'
import type { Theme } from './theme'
import { flattenCards, orderCards } from './el/layout/util'
import type { LayoutOrder } from './el/layout/util'
import type { FactorSites } from '.'

export type EditorState = {
  stateCardId: string
  statePageId: string
  stateRegionKey: 'header' | 'page' | 'footer'
  storeCardOrder: string[]
}

const regionKeys = ['header', 'page', 'footer'] as const
function initRegionCard(r: Partial<TableCardConfig> = {}, regionKey: typeof regionKeys[number], theme?: Theme) {
  return new Card({ cardId: objectId({ prefix: 'reg' }), ...r, regionKey, theme })
}

function ensureRegions(args: { regions?: Partial<TableCardConfig>[], theme?: Theme }) {
  const { regions = [], theme } = args
  regionKeys.forEach((regionKey) => {
    if (!regions.find(r => r.regionKey === regionKey))
      regions.push({ regionKey, slug: regionKey === 'page' ? '_home' : undefined })
  })

  return regions.map(r => initRegionCard(r, r.regionKey || 'page', theme))
}

export type SiteSettings = {
  factorSites: FactorSites
} & Partial<TableSiteConfig>

export class Site<T extends SiteSettings = SiteSettings> extends FactorObject<T> {
  siteId = this.settings.siteId || objectId({ prefix: 'site' })
  regionKeys = regionKeys
  themeId = vue.ref(this.settings.themeId)
  title = vue.ref(this.settings.title)
  status = vue.ref(this.settings.status)
  slug = vue.ref(this.settings.slug)
  domain = vue.ref(this.settings.domain)
  isAnimationDisabled = vue.ref(false)
  theme = vue.computed(() => {
    const themeConfig = themes.find(t => t.themeId === this.themeId.value)
    return themeConfig
  })

  regions = vue.shallowRef(ensureRegions({ regions: this.settings.regions || [], theme: this.theme.value }))
  regionsOrdered = vue.computed(() => {
    const out = this.regions.value.sort((a, b) => {
      const o = this.editor.storeCardOrder || []
      return o.indexOf(a.cardId) - o.indexOf(b.cardId)
    })
    return out
  })

  previewUrl = vue.computed(() => {
    const appUrl = this.settings.factorSites.factorApp.appUrl.value
    return `${appUrl}/site-preview/${this.siteId}`
  })

  obj = {
    headers: vue.computed(() => this.regions.value.filter(r => r.regionKey === 'header')),
    pages: vue.computed(() => this.regionsOrdered.value.filter(r => r.regionKey === 'page')),
    footers: vue.computed(() => this.regions.value.filter(r => r.regionKey === 'footer')),
  }

  layout = {
    header: vue.computed(() => this.obj.headers.value[0]),
    page: vue.computed(() => this.obj.pages.value.find(r => r.cardId === this.activePageId.value)),
    footer: vue.computed(() => this.obj.footers.value[0]),
  }

  allLayoutCards = vue.computed<Card[]>(() => {
    return Object.values(this.layout).flatMap((r) => {
      return flattenCards(r.value?.cards.value || [])
    }).filter(_ => _)
  })

  editor = vue.reactive<EditorState>({
    stateCardId: '',
    statePageId: '',
    stateRegionKey: 'page',
    storeCardOrder: [],
    ...this.settings.editor,
  })

  editorStored = vue.computed(() => {
    // get object of keys with store in them
    const storeKeys = Object.keys(this.editor).filter(k => k.includes('store'))
    const out = {} as Record<string, unknown>
    storeKeys.forEach((k) => {
      out[k] = this.editor[k as keyof EditorState] || []
    })
    return out
  })

  toConfig(): { siteId: string } & Partial<TableSiteConfig> {
    const { factorSites: _, ...savedSettings } = this.settings
    const regions = this.regions.value.map(p => p.toConfig())

    return {
      ...savedSettings,
      editor: this.editorStored.value,
      siteId: this.siteId,
      themeId: this.themeId.value,
      status: this.status.value,
      title: this.title.value,
      slug: this.slug.value,
      domain: this.domain.value,
      regions,
    }
  }

  activeCard = vue.computed(() => this.allLayoutCards.value.find(c => c.cardId === this.editor.stateCardId))
  activeCardConfig = vue.computed({
    get: () => this.allLayoutCards.value.find(c => c.cardId === this.editor.stateCardId)?.toConfig(),
    set: (v) => {
      if (v) {
        const c = this.allLayoutCards.value.find(c => c.cardId === this.editor.stateCardId)
        if (c)
          c.update(v)
      }
    },
  })

  async updateLayout(args: { order: LayoutOrder[] }) {
    const { order } = args

    this.isAnimationDisabled.value = true

    const availableCards = this.allLayoutCards.value
    order.forEach((regionOrder) => {
      const ind = this.regions.value.findIndex(r => r.cardId === regionOrder.itemId)
      if (ind > -1) {
        const ordered = orderCards({
          availableCards,
          order: regionOrder.items || [],
        })

        this.regions.value[ind].cards.value = ordered
      }
    })

    await this.utils.waitFor(100)

    this.isAnimationDisabled.value = false
  }

  activePageId = vue.computed({
    get: () => {
      let out: string
      const pgs = this.obj.pages.value
      const existsActivePage = this.editor.statePageId && pgs.some(p => p.cardId === this.editor.statePageId)

      if (existsActivePage) { out = this.editor.statePageId }

      else {
        const slugMap = pgs.reduce((acc, cur) => {
          acc[cur.slug.value || ''] = cur.cardId || ''
          return acc
        }, {} as Record<string, string>)
        return slugMap._home || slugMap[''] || pgs[0]?.cardId || ''
      }

      return out
    },
    set: (v) => {
      this.editor.statePageId = v
    },
  })

  setActivePageId(args: { slug?: string } = {}) {
    const { slug = '' } = args
    const r = this.obj.pages.value.find(r => r.settings.slug === slug || (!slug && (r.settings.slug === '_home' || !r.settings.slug)))
    return r?.cardId || this.obj.pages.value[0]?.cardId
  }

  activeRegionKey = vue.ref<typeof regionKeys[number]>('page')

  constructor(settings: T) {
    super('Site', settings)

    this.init()
  }

  init() {
    this.setActivePageId()
  }

  editPage = vue.reactive({
    selectedPageId: '',
    tempPage: {} as TableCardConfig,
  })

  editPageConfig = vue.computed({
    get: () => {
      if (!this.editPage.selectedPageId)
        return this.editPage.tempPage
      else
        return this.regions.value.find(r => r.cardId === this.editPage.selectedPageId)?.toConfig()
    },
    set: (v) => {
      if (v?.cardId) {
        const ind = this.regions.value.findIndex(r => r.cardId === v?.cardId)
        if (ind > -1)
          this.regions.value[ind].update(v || {})
      }
      else { this.editPage.tempPage = v || {} }
    },
  })

  useEditPage(args: { cardId?: string } = {}) {
    const { cardId } = args

    this.editPage.selectedPageId = cardId || ''

    this.settings.factorSites.useTool({ toolId: cardId ? 'editPage' : 'createPage' })
  }

  updateRegion(args: { cardConfig: Partial<TableCardConfig>, regionKey?: typeof regionKeys[number] }) {
    const { cardConfig, regionKey = 'page' } = args

    const r = initRegionCard(cardConfig, regionKey, this.theme.value)

    const i = this.regions.value.findIndex(r => r.cardId === cardConfig.cardId)
    if (i > -1)
      this.regions.value[i] = r
    else
      this.regions.value = [r, ...this.regions.value]
  }

  removeCard(args: { cardId: string }) {

  }

  addCard(args: { templateKey: string, addToCardId?: string, delay?: number, cardId?: string }) {
    const { templateKey, addToCardId, delay, cardId = objectId({ prefix: 'ca' }) } = args
    const regionKey = this.editor.stateRegionKey
    const cardConfig = { templateKey, cardId }
    const addCardAction = () => {
      if (addToCardId) {
        const card = this.allLayoutCards.value.find(c => c.cardId === addToCardId)
        if (card)
          card.addCard({ cardConfig })
      }
      else {
        const regionId = this.layout[regionKey].value?.cardId
        const ind = this.regions.value.findIndex(r => r.cardId === regionId)
        if (ind > -1)
          this.regions.value[ind].addCard({ cardConfig })
      }

      this.editor.stateCardId = cardId
    }

    if (delay && delay > 0)
      setTimeout(addCardAction, delay)

    else
      addCardAction()
  }

  async requestManageRegion(args: {
    _action: 'upsert' | 'delete'
    regionCard?: Partial<TableCardConfig>
    delay: number
    successMessage?: string
  }) {
    const { _action, regionCard, delay = 400, successMessage } = args

    if (!_action || !_action.length)
      throw new Error('no action')

    if (!regionCard)
      throw new Error('no region')

    const r = await this.settings.factorSites.requests.ManageRegion.projectRequest({
      _action,
      fields: { siteId: this.siteId, ...regionCard },
      successMessage,
    })

    const updateRegionAction = () => {
      if (_action === 'delete') {
        const i = this.regions.value.findIndex(r => r.cardId === regionCard.cardId)

        if (i > -1)
          this.regions.value.splice(i, 1)
      }
      else if (r.data && r.data.cardId) {
        this.updateRegion({ cardConfig: r.data })

        this.activePageId.value = r.data.cardId
      }
    }

    if (r.status === 'success') {
      if (delay && delay > 0)
        setTimeout(() => updateRegionAction(), delay)

      else
        updateRegionAction()
    }
    return r.data
  }

  setActiveCardDrawer(_args: { cardId?: string }) {}
  setActiveCardId(_args: { cardId?: string, drawer?: string, scrollIntoView?: boolean }) {}
  deleteCardById(_args: { cardId?: string }) {}

  update(config: Partial<T>) {
    const entries = Object.entries(config)
    entries.forEach(([key, value]) => {
      if (value !== undefined && vue.isRef(this[key as keyof this]))
        (this[key as keyof this] as vue.Ref).value = value

      this.settings = { ...this.settings, [key as keyof T]: value }
    })
  }

  async requestManageSite(args: { _action: 'update', saveOnlyKeys?: (keyof TableSiteConfig)[] }) {
    const { _action, saveOnlyKeys } = args

    const config = this.toConfig()

    if (!config.siteId)
      throw new Error('no siteId')

    let fields: Partial<TableSiteConfig> = { siteId: config.siteId }
    if (!saveOnlyKeys) {
      fields = config
    }
    else {
      saveOnlyKeys.forEach(<T extends keyof TableSiteConfig>(key: T) => {
        fields[key] = config[key]
      })
    }

    const r = await this.settings.factorSites.requests.ManageSite.projectRequest({
      _action,
      fields: fields as TableSiteConfig,
    })

    return r.data
  }
}

/**
 * Special Types
 */
type CreateTuple<T extends readonly CardTemplate[]> = {
  [P in keyof T]: T[P] extends CardTemplate<infer X, infer Q> ? [X, InstanceType<Q>['$props']['card'] ] : never
}[number]

type TupleToObject<T extends [string, unknown]> = {
  [P in T[0]]: T extends [P, Card<infer B>] ? B : never
}

export type CreateUserConfigs<T extends readonly CardTemplate[]> = TupleToObject<
  CreateTuple<T>
>
