import {
  type TuiPlugin,
  type TuiPluginApi,
  type TuiPluginInstallResult,
  type TuiPluginMeta,
  type TuiPluginModule,
  type TuiPluginStatus,
} from "@saeeol/plugin/tui"
import path from "path"
import { fileURLToPath } from "url"
import * as Log from "@saeeol/core/util/log"
import { errorData, errorMessage } from "@/util/error"
import { isRecord } from "@/util/record"
import { type PluginSource } from "@/plugin/shared"
import { PluginMeta } from "@/plugin/meta"
import { ConfigPlugin } from "@/config/plugin"
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
import type { HostPluginApi, HostSlots } from "./slots"

export type PluginLoad = {
  options: ConfigPlugin.Options | undefined
  spec: string
  target: string
  retry: boolean
  source: PluginSource | "internal"
  id: string
  module: TuiPluginModule
  origin: ConfigPlugin.Origin
  theme_root: string
  theme_files: string[]
}

export type Api = HostPluginApi

export type PluginScope = {
  lifecycle: TuiPluginApi["lifecycle"]
  track: (fn: (() => void) | undefined) => () => void
  dispose: () => Promise<void>
}

export type PluginEntry = {
  id: string
  load: PluginLoad
  meta: TuiPluginMeta
  themes: Record<string, PluginMeta.Theme>
  plugin: TuiPlugin
  enabled: boolean
  scope?: PluginScope
}

export type RuntimeState = {
  directory: string
  api: Api
  slots: HostSlots
  plugins: PluginEntry[]
  plugins_by_id: Map<string, PluginEntry>
  pending: Map<string, ConfigPlugin.Origin>
}

export const log = Log.create({ service: "tui.plugin" })
export const DISPOSE_TIMEOUT_MS = 5000
export const KV_KEY = "plugin_enabled"
export const EMPTY_TUI: TuiPluginModule = {
  tui: async () => {},
}

export function fail(message: string, data: Record<string, unknown>) {
  if (!("error" in data)) {
    log.error(message, data)
    console.error(`[tui.plugin] ${message}`, data)
    return
  }
  const text = `${message}: ${errorMessage(data.error)}`
  const next = { ...data, error: errorData(data.error) }
  log.error(text, next)
  console.error(`[tui.plugin] ${text}`, next)
}

export function warn(message: string, data: Record<string, unknown>) {
  log.warn(message, data)
  console.warn(`[tui.plugin] ${message}`, data)
}

export type CleanupResult = { type: "ok" } | { type: "error"; error: unknown } | { type: "timeout" }

export function runCleanup(fn: () => unknown, ms: number): Promise<CleanupResult> {
  return new Promise((resolve) => {
    const timer = setTimeout(() => {
      resolve({ type: "timeout" })
    }, ms)
    Promise.resolve()
      .then(fn)
      .then(
        () => {
          resolve({ type: "ok" })
        },
        (error) => {
          resolve({ type: "error", error })
        },
      )
      .finally(() => {
        clearTimeout(timer)
      })
  })
}

export function isTheme(value: unknown) {
  if (!isRecord(value)) return false
  if (!("theme" in value)) return false
  if (!isRecord(value.theme)) return false
  return true
}

export function resolveRoot(root: string) {
  if (root.startsWith("file://")) {
    const file = fileURLToPath(root)
    if (root.endsWith("/")) return file
    return path.dirname(file)
  }
  if (path.isAbsolute(root)) return root
  return path.resolve(process.cwd(), root)
}

export function readPluginEnabledMap(value: unknown) {
  if (!isRecord(value)) return {}
  return Object.fromEntries(
    Object.entries(value).filter((item): item is [string, boolean] => typeof item[1] === "boolean"),
  )
}

export function pluginEnabledState(state: RuntimeState, config: TuiConfig.Info) {
  return {
    ...readPluginEnabledMap(config.plugin_enabled),
    ...readPluginEnabledMap(state.api.kv.get(KV_KEY, {})),
  }
}

export function writePluginEnabledState(api: Api, id: string, enabled: boolean) {
  api.kv.set(KV_KEY, {
    ...readPluginEnabledMap(api.kv.get(KV_KEY, {})),
    [id]: enabled,
  })
}

export function addPluginEntry(state: RuntimeState, plugin: PluginEntry) {
  if (state.plugins_by_id.has(plugin.id)) {
    fail("duplicate tui plugin id", {
      id: plugin.id,
      path: plugin.load.spec,
    })
    return false
  }
  state.plugins_by_id.set(plugin.id, plugin)
  state.plugins.push(plugin)
  return true
}

export function applyInitialPluginEnabledState(state: RuntimeState, config: TuiConfig.Info) {
  const map = pluginEnabledState(state, config)
  for (const plugin of state.plugins) {
    const enabled = map[plugin.id]
    if (enabled === undefined) continue
    plugin.enabled = enabled
  }
}

export function listPluginStatus(state: RuntimeState): TuiPluginStatus[] {
  return state.plugins.map((plugin) => ({
    id: plugin.id,
    source: plugin.meta.source,
    spec: plugin.meta.spec,
    target: plugin.meta.target,
    enabled: plugin.enabled,
    active: plugin.scope !== undefined,
  }))
}

export const pluginOps = {
  add: undefined as ((state: RuntimeState | undefined, raw: string) => Promise<boolean>) | undefined,
  install: undefined as ((state: RuntimeState | undefined, raw: string, global?: boolean) => Promise<TuiPluginInstallResult>) | undefined,
}
