import { getScript } from '@/data/script'
import { globalStore } from '@/stores/global'
import { showSettings, activeSettingsPage } from '@/composables/settings'
import { runSequentially, parseAssignees } from '@/utils'
import { createDocumentResource, createResource, toast } from 'frappe-ui'
import { ref, reactive } from 'vue'

const documentsCache = {}
const controllersCache = {}
const assigneesCache = {}

export function useDocument(doctype, docname) {
  const { setupScript, scripts } = getScript(doctype)

  documentsCache[doctype] = documentsCache[doctype] || {}

  const error = ref('')

  if (!documentsCache[doctype][docname || '']) {
    if (docname) {
      documentsCache[doctype][docname] = createDocumentResource({
        doctype: doctype,
        name: docname,
        onSuccess: async () => await setupFormScript(),
        onError: (err) => {
          error.value = err
          if (err.exc_type === 'DoesNotExistError') {
            toast.error(__(err.messages[0] || 'Document does not exist'))
          }
          if (err.exc_type === 'PermissionError') {
            toast.error(
              __(
                err.messages[0] ||
                  'You do not have permission to access this document',
              ),
            )
          }
        },
        setValue: {
          onSuccess: () => {
            triggerOnSave()
            toast.success(__('Document updated successfully'))
          },
          onError: (err) => {
            triggerOnError(err)

            if (err.exc_type == 'MandatoryError') {
              const fieldName = err.messages
                .map((msg) => {
                  let arr = msg.split(': ')
                  return arr[arr.length - 1].trim()
                })
                .join(', ')
              toast.error(__('Mandatory field error: {0}', [fieldName]))
              return
            }

            err.messages?.forEach((msg) => {
              toast.error(msg)
            })

            if (err.messages?.length === 0) {
              toast.error(__('An error occurred while updating the document'))
            }

            console.error(err)
          },
        },
      })
    } else {
      documentsCache[doctype][''] = reactive({
        doc: {},
      })
      setupFormScript()
    }
  }

  assigneesCache[doctype] = assigneesCache[doctype] || {}

  if (!assigneesCache[doctype][docname || '']) {
    assigneesCache[doctype][docname || ''] = createResource({
      url: 'crm.api.doc.get_assigned_users',
      cache: `assignees:${doctype}:${docname}`,
      auto: docname ? true : false,
      params: {
        doctype: doctype,
        name: docname,
      },
      transform: (data) => parseAssignees(data),
    })
  }

  async function setupFormScript() {
    if (
      controllersCache[doctype] &&
      typeof controllersCache[doctype][docname || ''] === 'object'
    ) {
      return
    }

    if (!controllersCache[doctype]) {
      controllersCache[doctype] = {}
    }

    controllersCache[doctype][docname || ''] = {}

    const { makeCall } = globalStore()

    let helpers = {}

    helpers.crm = {
      makePhoneCall: makeCall,
      openSettings: (page) => {
        showSettings.value = true
        activeSettingsPage.value = page
      },
    }

    const controllersArray = await setupScript(
      documentsCache[doctype][docname || ''],
      helpers,
    )

    if (!controllersArray || controllersArray.length === 0) return

    const organizedControllers = {}
    for (const controller of controllersArray) {
      const controllerKey = controller.constructor.name // e.g., "CRMLead", "CRMProducts"
      if (!organizedControllers[controllerKey]) {
        organizedControllers[controllerKey] = []
      }
      organizedControllers[controllerKey].push(controller)
    }
    controllersCache[doctype][docname || ''] = organizedControllers

    triggerOnLoad()
  }

  function getControllers(row = null) {
    const _doctype = row?.doctype || doctype
    const controllerKey = _doctype.replace(/\s+/g, '')

    const docControllers = controllersCache[doctype]?.[docname || '']

    if (
      typeof docControllers === 'object' &&
      docControllers !== null &&
      !Array.isArray(docControllers)
    ) {
      return docControllers[controllerKey] || []
    }
    return []
  }

  async function triggerOnLoad() {
    const handler = async function () {
      await (this.onLoad?.() || this.on_load?.() || this.onload?.())
    }
    await trigger(handler)
  }

  async function triggerOnBeforeCreate() {
    const args = Array.from(arguments)
    const handler = async function () {
      await (this.onBeforeCreate?.(...args) || this.on_before_create?.(...args))
    }
    await trigger(handler)
  }

  async function triggerOnSave() {
    const handler = async function () {
      await (this.onSave?.() || this.on_save?.())
    }
    await trigger(handler)
  }

  async function triggerOnError() {
    const handler = async function () {
      await (this.onError?.() || this.on_error?.())
    }
    await trigger(handler)
  }

  async function triggerOnRefresh() {
    const handler = async function () {
      await this.refresh?.()
    }
    await trigger(handler)
  }

  async function triggerOnChange(fieldname, value, row) {
    let oldValue = null
    if (row) {
      oldValue = row[fieldname]
      row[fieldname] = value
    } else {
      oldValue = documentsCache[doctype][docname || ''].doc[fieldname]
      documentsCache[doctype][docname || ''].doc[fieldname] = value
    }

    const handler = async function () {
      this.value = value
      this.oldValue = oldValue
      if (row) {
        this.currentRowIdx = row.idx
      }
      await this[fieldname]?.()
    }

    try {
      await trigger(handler, row)
    } catch (error) {
      if (row) {
        row[fieldname] = oldValue
      } else {
        documentsCache[doctype][docname || ''].doc[fieldname] = oldValue
      }
      console.error(handler)
      throw error
    }
  }

  async function triggerOnRowAdd(row) {
    const handler = async function () {
      this.currentRowIdx = row.idx
      this.value = row
      await this[row.parentfield + '_add']?.()
    }

    await trigger(handler, row)
  }

  async function triggerOnRowRemove(selectedRows, rows) {
    const handler = async function () {
      if (selectedRows.size === 1) {
        const selectedRow = Array.from(selectedRows)[0]
        this.currentRowIdx = rows.find((r) => r.name === selectedRow).idx
      } else {
        delete this.currentRowIdx
      }

      this.selectedRows = Array.from(selectedRows)
      this.rows = rows

      await this[rows[0].parentfield + '_remove']?.()
    }

    await trigger(handler, rows[0])
  }

  async function triggerOnCreateLead() {
    const args = Array.from(arguments)
    const handler = async function () {
      await (this.onCreateLead?.(...args) || this.on_create_lead?.(...args))
    }
    await trigger(handler)
  }

  async function triggerConvertToDeal() {
    const args = Array.from(arguments)
    const handler = async function () {
      await (this.convertToDeal?.(...args) || this.convert_to_deal?.(...args))
    }
    await trigger(handler)
  }

  async function trigger(taskFn, row = null) {
    const controllers = getControllers(row)
    if (!controllers.length) return

    const tasks = controllers.map(
      (controller) => async () => await taskFn.call(controller),
    )

    await runSequentially(tasks)
  }

  return {
    document: documentsCache[doctype][docname || ''],
    assignees: assigneesCache[doctype][docname || ''],
    scripts,
    error,
    getControllers,
    triggerOnLoad,
    triggerOnBeforeCreate,
    triggerOnSave,
    triggerOnError,
    triggerOnRefresh,
    triggerOnChange,
    triggerOnRowAdd,
    triggerOnRowRemove,
    setupFormScript,
    triggerOnCreateLead,
    triggerConvertToDeal,
  }
}
