import { Component, For, Show, createMemo, lazy, onCleanup, onMount } from "solid-js"
import { createStore } from "solid-js/store"
import { makeEventListener } from "@solid-primitives/event-listener"
import { Button } from "@novaclaw/ui/button"
import { Icon } from "@novaclaw/ui/icon"
import { IconButton } from "@novaclaw/ui/icon-button"
import { TextField } from "@novaclaw/ui/text-field"
import { showToast } from "@/utils/toast"
import fuzzysort from "fuzzysort"
import { formatKeybind, parseKeybind, useCommand } from "@/context/command"
import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
import { SettingsList } from "./settings-list"

const ButtonV2 = lazy(() => import("@novaclaw/ui/v2/button-v2").then((module) => ({ default: module.ButtonV2 })))
const IconV2 = lazy(() => import("@novaclaw/ui/v2/icon").then((module) => ({ default: module.Icon })))
const IconButtonV2 = lazy(() =>
  import("@novaclaw/ui/v2/icon-button-v2").then((module) => ({ default: module.IconButtonV2 })),
)
const TextInputV2 = lazy(() =>
  import("@novaclaw/ui/v2/text-input-v2").then((module) => ({ default: module.TextInputV2 })),
)
const SettingsListV2 = lazy(() =>
  import("./settings-v2/parts/list").then((module) => ({ default: module.SettingsListV2 })),
)

const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
const PALETTE_ID = "command.palette"
const DEFAULT_PALETTE_KEYBIND = "mod+shift+p"

type KeybindGroup = "General" | "Session" | "Navigation" | "Model and agent" | "Terminal" | "Prompt"

type KeybindMeta = {
  title: string
  group: KeybindGroup
}

type KeybindMap = Record<string, string | undefined>
type CommandContext = ReturnType<typeof useCommand>

const GROUPS: KeybindGroup[] = ["General", "Session", "Navigation", "Model and agent", "Terminal", "Prompt"]

type GroupKey =
  | "settings.shortcuts.group.general"
  | "settings.shortcuts.group.session"
  | "settings.shortcuts.group.navigation"
  | "settings.shortcuts.group.modelAndAgent"
  | "settings.shortcuts.group.terminal"
  | "settings.shortcuts.group.prompt"

const groupKey: Record<KeybindGroup, GroupKey> = {
  General: "settings.shortcuts.group.general",
  Session: "settings.shortcuts.group.session",
  Navigation: "settings.shortcuts.group.navigation",
  "Model and agent": "settings.shortcuts.group.modelAndAgent",
  Terminal: "settings.shortcuts.group.terminal",
  Prompt: "settings.shortcuts.group.prompt",
}

function groupFor(id: string): KeybindGroup {
  if (id === PALETTE_ID) return "General"
  if (id.startsWith("terminal.")) return "Terminal"
  if (id.startsWith("model.") || id.startsWith("agent.") || id.startsWith("mcp.")) return "Model and agent"
  if (id.startsWith("file.") || id.startsWith("fileTree.")) return "Navigation"
  if (id.startsWith("prompt.")) return "Prompt"
  if (
    id.startsWith("session.") ||
    id.startsWith("message.") ||
    id.startsWith("permissions.") ||
    id.startsWith("steps.") ||
    id.startsWith("review.")
  )
    return "Session"

  return "General"
}

function isModifier(key: string) {
  return key === "Shift" || key === "Control" || key === "Alt" || key === "Meta"
}

function normalizeKey(key: string) {
  if (key === ",") return "comma"
  if (key === "+") return "plus"
  if (key === " ") return "space"
  return key.toLowerCase()
}

function recordKeybind(event: KeyboardEvent) {
  if (isModifier(event.key)) return

  const parts: string[] = []

  const mod = IS_MAC ? event.metaKey : event.ctrlKey
  if (mod) parts.push("mod")

  if (IS_MAC && event.ctrlKey) parts.push("ctrl")
  if (!IS_MAC && event.metaKey) parts.push("meta")
  if (event.altKey) parts.push("alt")
  if (event.shiftKey) parts.push("shift")

  const key = normalizeKey(event.key)
  if (!key) return
  parts.push(key)

  return parts.join("+")
}

function signatures(config: string | undefined) {
  if (!config) return []
  const sigs: string[] = []

  for (const kb of parseKeybind(config)) {
    const parts: string[] = []
    if (kb.ctrl) parts.push("ctrl")
    if (kb.alt) parts.push("alt")
    if (kb.shift) parts.push("shift")
    if (kb.meta) parts.push("meta")
    if (kb.key) parts.push(kb.key)
    if (parts.length === 0) continue
    sigs.push(parts.join("+"))
  }

  return sigs
}

function keybinds(value: unknown): KeybindMap {
  if (!value || typeof value !== "object" || Array.isArray(value)) return {}
  return value as KeybindMap
}

function listFor(command: CommandContext, map: KeybindMap, palette: string) {
  const out = new Map<string, KeybindMeta>()
  out.set(PALETTE_ID, { title: palette, group: "General" })

  for (const opt of command.catalog) {
    if (opt.id.startsWith("suggested.")) continue
    if (opt.hidden) continue
    out.set(opt.id, { title: opt.title, group: groupFor(opt.id) })
  }

  for (const opt of command.options) {
    if (opt.id.startsWith("suggested.")) continue
    if (opt.hidden) continue
    out.set(opt.id, { title: opt.title, group: groupFor(opt.id) })
  }

  for (const [id, value] of Object.entries(map)) {
    if (typeof value !== "string") continue
    if (out.has(id)) continue
    out.set(id, { title: id, group: groupFor(id) })
  }

  return out
}

function groupedFor(list: Map<string, KeybindMeta>) {
  const out = new Map<KeybindGroup, string[]>()
  for (const group of GROUPS) out.set(group, [])

  for (const [id, item] of list) {
    const ids = out.get(item.group)
    if (!ids) continue
    ids.push(id)
  }

  for (const group of GROUPS) {
    const ids = out.get(group)
    if (!ids) continue
    ids.sort((a, b) => (list.get(a)?.title ?? "").localeCompare(list.get(b)?.title ?? ""))
  }

  return out
}

function filteredFor(
  query: string,
  list: Map<string, KeybindMeta>,
  grouped: Map<KeybindGroup, string[]>,
  keybind: (id: string) => string,
) {
  const value = query.toLowerCase().trim()
  if (!value) return grouped

  const out = new Map<KeybindGroup, string[]>()
  for (const group of GROUPS) out.set(group, [])

  const items = Array.from(list.entries()).map(([id, meta]) => ({
    id,
    title: meta.title,
    group: meta.group,
    keybind: keybind(id),
  }))

  const results = fuzzysort.go(value, items, {
    keys: ["title", "keybind"],
    threshold: -10000,
  })

  for (const result of results) {
    const ids = out.get(result.obj.group)
    if (!ids) continue
    ids.push(result.obj.id)
  }

  return out
}

function useKeyCapture(input: {
  active: () => string | null
  stop: () => void
  set: (id: string, keybind: string) => void
  used: () => Map<string, { id: string; title: string }[]>
  language: ReturnType<typeof useLanguage>
}) {
  onMount(() => {
    const handle = (event: KeyboardEvent) => {
      const id = input.active()
      if (!id) return

      event.preventDefault()
      event.stopPropagation()
      event.stopImmediatePropagation()

      if (event.key === "Escape") {
        input.stop()
        return
      }

      const clear =
        (event.key === "Backspace" || event.key === "Delete") &&
        !event.ctrlKey &&
        !event.metaKey &&
        !event.altKey &&
        !event.shiftKey
      if (clear) {
        input.set(id, "none")
        input.stop()
        return
      }

      const next = recordKeybind(event)
      if (!next) return

      const conflicts = new Map<string, string>()
      for (const sig of signatures(next)) {
        for (const item of input.used().get(sig) ?? []) {
          if (item.id === id) continue
          conflicts.set(item.id, item.title)
        }
      }

      if (conflicts.size > 0) {
        showToast({
          title: input.language.t("settings.shortcuts.conflict.title"),
          description: input.language.t("settings.shortcuts.conflict.description", {
            keybind: formatKeybind(next, input.language.t),
            titles: [...conflicts.values()].join(", "),
          }),
        })
        return
      }

      input.set(id, next)
      input.stop()
    }

    makeEventListener(document, "keydown", handle, { capture: true })
  })
}

export const SettingsKeybinds: Component<{ v2?: boolean }> = (props) => {
  const command = useCommand()
  const language = useLanguage()
  const settings = useSettings()

  const [store, setStore] = createStore({
    active: null as string | null,
    filter: "",
  })

  const stop = () => {
    if (!store.active) return
    setStore("active", null)
    command.keybinds(true)
  }

  const start = (id: string) => {
    if (store.active === id) {
      stop()
      return
    }

    if (store.active) stop()

    setStore("active", id)
    command.keybinds(false)
  }

  const map = createMemo(() => keybinds(settings.current.keybinds))

  const hasOverrides = createMemo(() => Object.values(map()).some((x) => typeof x === "string"))

  const resetAll = () => {
    stop()
    settings.keybinds.resetAll()
    showToast({
      title: language.t("settings.shortcuts.reset.toast.title"),
      description: language.t("settings.shortcuts.reset.toast.description"),
    })
  }

  const list = createMemo(() => {
    language.locale()
    return listFor(command, map(), language.t("command.palette"))
  })

  const title = (id: string) => list().get(id)?.title ?? ""

  const grouped = createMemo(() => groupedFor(list()))

  const filtered = createMemo(() => {
    return filteredFor(store.filter, list(), grouped(), (id) => command.keybind(id) || "")
  })

  const hasResults = createMemo(() => {
    for (const group of GROUPS) {
      const ids = filtered().get(group) ?? []
      if (ids.length > 0) return true
    }
    return false
  })

  const used = createMemo(() => {
    const map = new Map<string, { id: string; title: string }[]>()

    const add = (key: string, value: { id: string; title: string }) => {
      const list = map.get(key)
      if (!list) {
        map.set(key, [value])
        return
      }
      list.push(value)
    }

    const palette = settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND
    for (const sig of signatures(palette)) {
      add(sig, { id: PALETTE_ID, title: title(PALETTE_ID) })
    }

    const valueFor = (id: string) => {
      const custom = settings.keybinds.get(id)
      if (typeof custom === "string") return custom

      const live = command.options.find((x) => x.id === id)
      if (live?.keybind) return live.keybind

      const meta = command.catalog.find((x) => x.id === id)
      return meta?.keybind
    }

    for (const id of list().keys()) {
      if (id === PALETTE_ID) continue
      for (const sig of signatures(valueFor(id))) {
        add(sig, { id, title: title(id) })
      }
    }

    return map
  })

  const setKeybind = (id: string, keybind: string) => settings.keybinds.set(id, keybind)

  useKeyCapture({
    active: () => store.active,
    stop,
    set: setKeybind,
    used,
    language,
  })

  onCleanup(() => {
    if (store.active) command.keybinds(true)
  })

  const emptyResults = (
    <Show when={store.filter && !hasResults()}>
      <div
        classList={{
          "flex flex-col items-center justify-center py-12 text-center": !props.v2,
          "settings-v2-shortcuts-status": props.v2,
        }}
      >
        <span
          classList={{
            "text-14-regular text-text-weak": !props.v2,
          }}
        >
          {language.t("settings.shortcuts.search.empty")}
        </span>
        <Show when={store.filter}>
          <span
            classList={{
              "text-14-regular text-text-strong mt-1": !props.v2,
              "settings-v2-shortcuts-status-filter": props.v2,
            }}
          >
            &quot;{store.filter}&quot;
          </span>
        </Show>
      </div>
    </Show>
  )

  const List = props.v2 ? SettingsListV2 : SettingsList

  const groups = (
    <div
      classList={{
        "settings-v2-shortcuts flex flex-col gap-8": props.v2,
        "flex flex-col gap-8 max-w-[720px]": !props.v2,
      }}
    >
      <For each={GROUPS}>
        {(group) => (
          <Show when={(filtered().get(group) ?? []).length > 0}>
            <div
              classList={{
                "settings-v2-section": props.v2,
                "flex flex-col gap-1": !props.v2,
              }}
            >
              <h3
                classList={{
                  "settings-v2-section-title": props.v2,
                  "text-14-medium text-text-strong pb-2": !props.v2,
                }}
              >
                {language.t(groupKey[group])}
              </h3>
              <List>
                <For each={filtered().get(group) ?? []}>
                  {(id) => (
                    <div class="flex items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
                      <span
                        classList={{
                          "text-14-regular text-text-strong": !props.v2,
                        }}
                      >
                        {title(id)}
                      </span>
                      <button
                        type="button"
                        data-keybind-id={id}
                        classList={{
                          "settings-v2-keybind-button": props.v2,
                          "settings-v2-keybind-button--active": props.v2 && store.active === id,
                          "h-8 px-3 rounded-md text-12-regular": !props.v2,
                          "bg-surface-base text-text-subtle hover:bg-surface-raised-base-hover active:bg-surface-raised-base-active":
                            !props.v2 && store.active !== id,
                          "border border-border-weak-base bg-surface-inset-base text-text-weak":
                            !props.v2 && store.active === id,
                        }}
                        onClick={() => start(id)}
                      >
                        <Show
                          when={store.active === id}
                          fallback={command.keybind(id) || language.t("settings.shortcuts.unassigned")}
                        >
                          {language.t("settings.shortcuts.pressKeys")}
                        </Show>
                      </button>
                    </div>
                  )}
                </For>
              </List>
            </div>
          </Show>
        )}
      </For>
      {emptyResults}
    </div>
  )

  return (
    <Show
      when={props.v2}
      fallback={
        <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
          <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
            <div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
              <div class="flex items-center justify-between gap-4">
                <h2 class="text-16-medium text-text-strong">{language.t("settings.shortcuts.title")}</h2>
                <Button size="small" variant="secondary" onClick={resetAll} disabled={!hasOverrides()}>
                  {language.t("settings.shortcuts.reset.button")}
                </Button>
              </div>

              <div class="flex items-center gap-2 px-3 h-9 rounded-lg bg-surface-base">
                <Icon name="magnifying-glass" class="text-icon-weak-base flex-shrink-0" />
                <TextField
                  variant="ghost"
                  type="text"
                  value={store.filter}
                  onChange={(v) => setStore("filter", v)}
                  placeholder={language.t("settings.shortcuts.search.placeholder")}
                  spellcheck={false}
                  autocorrect="off"
                  autocomplete="off"
                  autocapitalize="off"
                  class="flex-1"
                />
                <Show when={store.filter}>
                  <IconButton icon="circle-x" variant="ghost" onClick={() => setStore("filter", "")} />
                </Show>
              </div>
            </div>
          </div>
          {groups}
        </div>
      }
    >
      <>
        <div class="settings-v2-tab-header settings-v2-tab-header--stacked">
          <div class="settings-v2-tab-header-row">
            <h2 class="settings-v2-tab-title">{language.t("settings.shortcuts.title")}</h2>
            <ButtonV2 variant="ghost" onClick={resetAll} disabled={!hasOverrides()}>
              {language.t("settings.shortcuts.reset.button")}
            </ButtonV2>
          </div>
          <div class="settings-v2-tab-search">
            <TextInputV2
              type="search"
              appearance="base"
              value={store.filter}
              onInput={(event) => setStore("filter", event.currentTarget.value)}
              placeholder={language.t("settings.shortcuts.search.placeholder")}
              spellcheck={false}
              autocorrect="off"
              autocomplete="off"
              autocapitalize="off"
              aria-label={language.t("settings.shortcuts.search.placeholder")}
            />
            <Show when={store.filter}>
              <IconButtonV2
                type="button"
                variant="ghost-muted"
                size="small"
                class="settings-v2-tab-search-clear"
                icon={<IconV2 name="close" size="large" class="text-v2-icon-icon-muted" />}
                onClick={() => setStore("filter", "")}
              />
            </Show>
          </div>
        </div>
        <div class="settings-v2-tab-body">{groups}</div>
      </>
    </Show>
  )
}
