/** 세션 커맨드 등록 — index.tsx에서 분리 */

import path from "path"
import type { ScrollBoxRenderable } from "@opentui/core"
import type { PromptRef } from "@tui/component/prompt"
import type { DialogContext } from "@tui/ui/dialog"
import type { Session as SessionInfo, Message } from "@saeeol/sdk/v2"
import * as Clipboard from "../../util/clipboard"
import { DialogConfirm } from "@tui/ui/dialog-confirm"
import { DialogAlert } from "@tui/ui/dialog-alert"
import { DialogPointing } from "./dialog-pointing"
import { DialogTimeline } from "./dialog-timeline"
import { DialogForkFromTimeline } from "./dialog-fork-from-timeline"
import { DialogSessionRename } from "../../component/dialog-session-rename"
import { DialogExportOptions } from "../../ui/dialog-export-options"
import { submitFeedback } from "@/saeeol/cli/cmd/tui/feedback"
import * as Editor from "../../util/editor"
import { Filesystem } from "@/util/filesystem"
import { formatTranscript } from "../../util/transcript"
import { t } from "@/util/i18n"

export interface CommandDeps {
  session: () => SessionInfo | undefined
  messages: () => Message[]
  route: { sessionID: string; type: string; prompt?: any }
  scroll: any
  prompt: PromptRef | undefined
  children: () => SessionInfo[]
  sync: any
  sdk: any
  toast: any
  kv: any
  dialog: any
  renderer: any
  keybind: any
  local: any
  sidebarVisible: () => boolean
  setSidebar: (next: any) => void
  setSidebarOpen: (v: boolean) => void
  setConceal: (next: any) => void
  setTimestamps: (next: any) => void
  setShowThinking: (next: any) => void
  setShowDetails: (next: any) => void
  setShowScrollbar: (next: any) => void
  setShowGenericToolOutput: (next: any) => void
  showThinking: () => boolean
  showDetails: () => boolean
  showAssistantMetadata: () => boolean
  conceal: () => boolean
  showTimestamps: () => boolean
  showGenericToolOutput: () => boolean
  toBottom: () => void
  navigate: (route: any) => void
  pointing: () => string | undefined
  setPointing: (next: any) => void
}

export function registerSessionCommands(
  command: { register: (fn: () => any[]) => void },
  deps: CommandDeps,
) {
  const {
    session, messages, route, children, sync, sdk, toast, kv,
    dialog, renderer, keybind, local, sidebarVisible, setSidebar, setSidebarOpen,
    setConceal, setTimestamps, setShowThinking, setShowDetails, setShowScrollbar,
    setShowGenericToolOutput, showThinking, showDetails, showAssistantMetadata,
    conceal, showTimestamps, showGenericToolOutput, toBottom, navigate,
    pointing, setPointing,
  } = deps

  // NOTE: deps.scroll / deps.prompt are mutable refs — use deps.* directly
  function findNextVisible(direction: "next" | "prev"): string | null {
    const scroll = deps.scroll
    const list = messages()
    const children = scroll.getChildren()
    const visible = children
      .filter((c: any) => {
        if (!c.id) return false
        const msg = list.find((m: any) => m.id === c.id)
        if (!msg) return false
        const parts = sync.data.part[msg.id]
        return parts?.some((p: any) => p?.type === "text" && !p.synthetic && !p.ignored)
      })
      .sort((a: any, b: any) => a.y - b.y)
    if (visible.length === 0) return null
    if (direction === "next") return visible.find((c: any) => c.y > scroll.y + 10)?.id ?? null
    return [...visible].reverse().find((c: any) => c.y < scroll.y - 10)?.id ?? null
  }

  function scrollToMessage(direction: "next" | "prev", d: DialogContext) {
    const targetID = findNextVisible(direction)
    const scroll = deps.scroll
    if (!targetID) { scroll.scrollBy(direction === "next" ? scroll.height : -scroll.height); d.clear(); return }
    const child = scroll.getChildren().find((c: any) => c.id === targetID)
    if (child) scroll.scrollBy(child.y - scroll.y - 1)
    d.clear()
  }

  function moveFirstChild() {
    if (children().length === 1) return
    const next = children().find((x: any) => !!x.parentID)
    if (next) navigate({ type: "session", sessionID: next.id })
  }

  function moveChild(dir: number) {
    if (children().length === 1) return
    const sessions = children().filter((x: any) => !!x.parentID)
    let idx = sessions.findIndex((x: any) => x.id === session()?.id) - dir
    if (idx >= sessions.length) idx = 0
    if (idx < 0) idx = sessions.length - 1
    if (sessions[idx]) navigate({ type: "session", sessionID: sessions[idx].id })
  }

  function childHandler(fn: (d: DialogContext) => void) {
    return (d: DialogContext) => {
      if (!session()?.parentID || dialog.stack.length > 0) return
      fn(d)
    }
  }

  command.register(() => [
    {
      title: t("cmd.session.pointing"),
      description: pointing() ? t("cmd.session.pointing.toast.set", { path: pointing()! }) : t("cmd.session.pointing.description"),
      value: "session.pointing",
      category: t("cmd.cat.session"),
      slash: { name: "pointing", aliases: ["point"] },
      onSelect: async (d: DialogContext) => {
        const current = pointing() ?? process.cwd()
        const result = await (DialogPointing as any).show(d, {
          current,
          pointing: pointing(),
        })
        if (result === null) return
        const trimmed = result.trim()
        if (!trimmed) {
          setPointing(() => undefined)
          toast.show({ message: t("cmd.session.pointing.toast.cleared"), variant: "success" })
          d.clear()
          return
        }
        const stat = Filesystem.stat(trimmed)
        if (!stat) {
          dialog.replace(() => <DialogAlert title="⚠" message={t("cmd.session.pointing.error.notFound", { path: trimmed })} />)
          return
        }
        if (!stat.isDirectory()) {
          dialog.replace(() => <DialogAlert title="⚠" message={t("cmd.session.pointing.error.notDir")} />)
          return
        }
        setPointing(() => trimmed)
        toast.show({ message: t("cmd.session.pointing.toast.set", { path: trimmed }), variant: "success" })
        d.clear()
      },
    },
    {
      title: t("cmd.session.unpointing"),
      description: t("cmd.session.unpointing.description"),
      value: "session.unpointing",
      category: t("cmd.cat.session"),
      slash: { name: "unpointing", aliases: ["unpoint"] },
      enabled: !!pointing(),
      hidden: !pointing(),
      onSelect: (d: DialogContext) => {
        setPointing(() => undefined)
        toast.show({ message: t("cmd.session.pointing.toast.cleared"), variant: "success" })
        d.clear()
      },
    },
    {
      title: session()?.share?.url ? "Copy share link" : t("cmd.session.share"),
      value: "session.share", suggested: route.type === "session", keybind: "session_share", category: t("cmd.cat.session"),
      enabled: sync.data.config.share !== "disabled", slash: { name: "share" },
      onSelect: async (d: DialogContext) => {
        const copy = (url: string) => Clipboard.copy(url).then(() => toast.show({ message: "Share URL copied!", variant: "success" })).catch(() => toast.show({ message: "Failed to copy", variant: "error" }))
        const url = session()?.share?.url
        if (url) { await copy(url); d.clear(); return }
        if (!kv.get("share_consent", false)) {
          const ok = await DialogConfirm.show(d, "Share Session", "Are you sure?")
          if (ok !== true) return
          kv.set("share_consent", true)
        }
        await sdk.client.session.share({ sessionID: route.sessionID }).then((res: any) => copy(res.data!.share!.url)).catch((e: any) => toast.show({ message: e instanceof Error ? e.message : "Failed", variant: "error" }))
        d.clear()
      },
    },
    { title: t("cmd.session.rename"), value: "session.rename", keybind: "session_rename", category: t("cmd.cat.session"), slash: { name: "rename" }, onSelect: (d: DialogContext) => d.replace(() => <DialogSessionRename session={route.sessionID} />) },
    {
      title: t("cmd.session.timeline"), value: "session.timeline", keybind: "session_timeline", category: t("cmd.cat.session"), slash: { name: "timeline" },
      onSelect: (d: DialogContext) => d.replace(() => <DialogTimeline onMove={(id: string) => { const c = deps.scroll.getChildren().find((x: any) => x.id === id); if (c) deps.scroll.scrollBy(c.y - deps.scroll.y - 1) }} sessionID={route.sessionID} setPrompt={(pi: any) => deps.prompt?.set(pi)} />),
    },
    {
      title: t("cmd.session.fork"), value: "session.fork", keybind: "session_fork", category: t("cmd.cat.session"), slash: { name: "fork" },
      onSelect: (d: DialogContext) => d.replace(() => <DialogForkFromTimeline onMove={(id?: string) => { if (!id) return; const c = deps.scroll.getChildren().find((x: any) => x.id === id); if (c) deps.scroll.scrollBy(c.y - deps.scroll.y - 1) }} sessionID={route.sessionID} />),
    },
    {
      title: t("cmd.session.compact"), value: "session.compact", keybind: "session_compact", category: t("cmd.cat.session"), slash: { name: "compact", aliases: ["summarize"] },
      onSelect: (d: DialogContext) => {
        const m = local.model.current()
        if (!m) { toast.show({ variant: "warning", message: "Connect a provider to summarize", duration: 3000 }); return }
        void sdk.client.session.summarize({ sessionID: route.sessionID, modelID: m.modelID, providerID: m.providerID })
        d.clear()
      },
    },
    {
      title: t("cmd.session.unshare"), value: "session.unshare", keybind: "session_unshare", category: t("cmd.cat.session"), enabled: !!session()?.share?.url, slash: { name: "unshare" },
      onSelect: async (d: DialogContext) => {
        await sdk.client.session.unshare({ sessionID: route.sessionID }).then(() => toast.show({ message: "Unshared", variant: "success" })).catch((e: any) => toast.show({ message: e instanceof Error ? e.message : "Failed", variant: "error" }))
        d.clear()
      },
    },
    {
      title: t("cmd.session.undo"), value: "session.undo", keybind: "messages_undo", category: t("cmd.cat.session"), slash: { name: "undo" },
      onSelect: async (d: DialogContext) => {
        const status = sync.data.session_status?.[route.sessionID]
        if (status?.type !== "idle") await sdk.client.session.abort({ sessionID: route.sessionID }).catch(() => {})
        const rev = session()?.revert?.messageID
        const msg = messages().findLast((x: any) => (!rev || x.id < rev) && x.role === "user")
        if (!msg) return
        void sdk.client.session.revert({ sessionID: route.sessionID, messageID: msg.id }).then(() => toBottom())
        const parts = sync.data.part[msg.id]
        deps.prompt?.set(parts.reduce((agg: any, part: any) => { if (part.type === "text" && !part.synthetic) agg.input += part.text; if (part.type === "file") agg.parts.push(part); return agg }, { input: "", parts: [] }))
        d.clear()
      },
    },
    {
      title: t("cmd.session.redo"), value: "session.redo", keybind: "messages_redo", category: t("cmd.cat.session"), enabled: !!session()?.revert?.messageID, slash: { name: "redo" },
      onSelect: (d: DialogContext) => {
        d.clear()
        const messageID = session()?.revert?.messageID
        if (!messageID) return
        const msg = messages().find((x: any) => x.role === "user" && x.id > messageID)
        if (!msg) { void sdk.client.session.unrevert({ sessionID: route.sessionID }); deps.prompt?.set({ input: "", parts: [] }); return }
        void sdk.client.session.revert({ sessionID: route.sessionID, messageID: msg.id })
      },
    },
    { title: sidebarVisible() ? t("sidebar.toggle.hide") : t("sidebar.toggle.show"), value: "session.sidebar.toggle", keybind: "sidebar_toggle", category: t("cmd.cat.session"), onSelect: (d: DialogContext) => { const v = sidebarVisible(); setSidebar(() => (v ? "hide" : "auto")); setSidebarOpen(!v); d.clear() } },
    { title: conceal() ? t("cmd.session.conceal_disable") : t("cmd.session.conceal_enable"), value: "session.toggle.conceal", keybind: "messages_toggle_conceal", category: t("cmd.cat.session"), onSelect: (d: DialogContext) => { setConceal((p: boolean) => !p); d.clear() } },
    { title: showTimestamps() ? t("cmd.session.timestamps_hide") : t("cmd.session.timestamps_show"), value: "session.toggle.timestamps", category: t("cmd.cat.session"), slash: { name: "timestamps", aliases: ["toggle-timestamps"] }, onSelect: (d: DialogContext) => { setTimestamps((p: string) => (p === "show" ? "hide" : "show")); d.clear() } },
    { title: showThinking() ? t("cmd.session.thinking_hide") : t("cmd.session.thinking_show"), value: "session.toggle.thinking", keybind: "display_thinking", category: t("cmd.cat.session"), slash: { name: "thinking", aliases: ["toggle-thinking"] }, onSelect: (d: DialogContext) => { setShowThinking((p: boolean) => !p); d.clear() } },
    { title: showDetails() ? t("cmd.session.actions_hide") : t("cmd.session.actions_show"), value: "session.toggle.actions", keybind: "tool_details", category: t("cmd.cat.session"), onSelect: (d: DialogContext) => { setShowDetails((p: boolean) => !p); d.clear() } },
    { title: t("cmd.session.scrollbar_toggle"), value: "session.toggle.scrollbar", keybind: "scrollbar_toggle", category: t("cmd.cat.session"), onSelect: (d: DialogContext) => { setShowScrollbar((p: boolean) => !p); d.clear() } },
    { title: showGenericToolOutput() ? t("cmd.session.generic_output_hide") : t("cmd.session.generic_output_show"), value: "session.toggle.generic_tool_output", category: t("cmd.cat.session"), onSelect: (d: DialogContext) => { setShowGenericToolOutput((p: boolean) => !p); d.clear() } },
    { title: t("cmd.session.page_up"), value: "session.page.up", keybind: "messages_page_up", category: t("cmd.cat.session"), hidden: true, onSelect: (d: DialogContext) => { deps.scroll.scrollBy(-deps.scroll.height / 2); d.clear() } },
    { title: t("cmd.session.page_down"), value: "session.page.down", keybind: "messages_page_down", category: t("cmd.cat.session"), hidden: true, onSelect: (d: DialogContext) => { deps.scroll.scrollBy(deps.scroll.height / 2); d.clear() } },
    { title: t("cmd.session.line_up"), value: "session.line.up", keybind: "messages_line_up", category: t("cmd.cat.session"), hidden: true, disabled: true, onSelect: (d: DialogContext) => { deps.scroll.scrollBy(-1); d.clear() } },
    { title: t("cmd.session.line_down"), value: "session.line.down", keybind: "messages_line_down", category: t("cmd.cat.session"), hidden: true, disabled: true, onSelect: (d: DialogContext) => { deps.scroll.scrollBy(1); d.clear() } },
    { title: t("cmd.session.half_page_up"), value: "session.half.page.up", keybind: "messages_half_page_up", category: t("cmd.cat.session"), hidden: true, onSelect: (d: DialogContext) => { deps.scroll.scrollBy(-deps.scroll.height / 4); d.clear() } },
    { title: t("cmd.session.half_page_down"), value: "session.half.page.down", keybind: "messages_half_page_down", category: t("cmd.cat.session"), hidden: true, onSelect: (d: DialogContext) => { deps.scroll.scrollBy(deps.scroll.height / 4); d.clear() } },
    { title: t("cmd.session.first"), value: "session.first", keybind: "messages_first", category: t("cmd.cat.session"), hidden: true, onSelect: (d: DialogContext) => { deps.scroll.scrollTo(0); d.clear() } },
    { title: t("cmd.session.last"), value: "session.last", keybind: "messages_last", category: t("cmd.cat.session"), hidden: true, onSelect: (d: DialogContext) => { deps.scroll.scrollTo(deps.scroll.scrollHeight); d.clear() } },
    {
      title: t("cmd.session.last_user"), value: "session.messages_last_user", keybind: "messages_last_user", category: t("cmd.cat.session"), hidden: true,
      onSelect: () => {
        const msgs = sync.data.message[route.sessionID]
        if (!msgs?.length) return
        for (let i = msgs.length - 1; i >= 0; i--) {
          const msg = msgs[i]
          if (!msg || msg.role !== "user") continue
          const parts = sync.data.part[msg.id]
          if (parts?.some((p: any) => p?.type === "text" && !p.synthetic && !p.ignored)) {
            const c = deps.scroll.getChildren().find((x: any) => x.id === msg.id)
            if (c) deps.scroll.scrollBy(c.y - deps.scroll.y - 1)
            break
          }
        }
      },
    },
    { title: t("cmd.session.next_message"), value: "session.message.next", keybind: "messages_next", category: t("cmd.cat.session"), hidden: true, onSelect: (d: DialogContext) => scrollToMessage("next", d) },
    { title: t("cmd.session.prev_message"), value: "session.message.previous", keybind: "messages_previous", category: t("cmd.cat.session"), hidden: true, onSelect: (d: DialogContext) => scrollToMessage("prev", d) },
    {
      title: t("cmd.session.copy_message"), value: "messages.copy", keybind: "messages_copy", category: t("cmd.cat.session"),
      onSelect: (d: DialogContext) => {
        const rev = session()?.revert?.messageID
        const last = messages().findLast((m: any) => m.role === "assistant" && (!rev || m.id < rev))
        if (!last) { toast.show({ message: "No assistant messages found", variant: "error" }); d.clear(); return }
        const parts = sync.data.part[last.id] ?? []
        const text = parts.filter((p: any) => p.type === "text").map((p: any) => p.text).join("\n").trim()
        if (!text) { toast.show({ message: "No text content", variant: "error" }); d.clear(); return }
        Clipboard.copy(text).then(() => toast.show({ message: "Copied!", variant: "success" })).catch(() => toast.show({ message: "Failed", variant: "error" }))
        d.clear()
      },
    },
    { title: t("cmd.feedback.up"), value: "messages.feedback.up", keybind: "messages_feedback_up", category: t("cmd.cat.session"), onSelect: (d: DialogContext) => submitFeedback("up", d, { toast, session, messages }) },
    { title: t("cmd.feedback.down"), value: "messages.feedback.down", keybind: "messages_feedback_down", category: t("cmd.cat.session"), onSelect: (d: DialogContext) => submitFeedback("down", d, { toast, session, messages }) },
    {
      title: t("cmd.session.copy_transcript"), value: "session.copy", category: t("cmd.cat.session"), slash: { name: "copy" },
      onSelect: async (d: DialogContext) => {
        try {
          const s = session()
          if (!s) return
          await Clipboard.copy(formatTranscript(s, messages().map((m: any) => ({ info: m, parts: sync.data.part[m.id] ?? [] })), { thinking: showThinking(), toolDetails: showDetails(), assistantMetadata: showAssistantMetadata(), providers: sync.data.provider }))
          toast.show({ message: "Copied!", variant: "success" })
        } catch { toast.show({ message: "Failed", variant: "error" }) }
        d.clear()
      },
    },
    {
      title: t("cmd.session.export_transcript"), value: "session.export", keybind: "session_export", category: t("cmd.cat.session"), slash: { name: "export" },
      onSelect: async (d: DialogContext) => {
        try {
          const s = session()
          if (!s) return
          const opts = await DialogExportOptions.show(d, `session-${s.id.slice(0, 8)}.md`, showThinking(), showDetails(), showAssistantMetadata(), false)
          if (!opts) return
          const transcript = formatTranscript(s, messages().map((m: any) => ({ info: m, parts: sync.data.part[m.id] ?? [] })), { thinking: opts.thinking, toolDetails: opts.toolDetails, assistantMetadata: opts.assistantMetadata, providers: sync.data.provider })
          if (opts.openWithoutSaving) { await Editor.open({ value: transcript, renderer }) }
          else {
            const fp = path.join(process.cwd(), opts.filename.trim())
            await Filesystem.write(fp, transcript)
            const result = await Editor.open({ value: transcript, renderer })
            if (result !== undefined) await Filesystem.write(fp, result)
            toast.show({ message: `Exported to ${opts.filename}`, variant: "success" })
          }
        } catch { toast.show({ message: "Failed to export", variant: "error" }) }
        d.clear()
      },
    },
    { title: t("cmd.session.child_first"), value: "session.child.first", keybind: "session_child_first", category: t("cmd.cat.session"), hidden: true, onSelect: (d: DialogContext) => { moveFirstChild(); d.clear() } },
    { title: t("cmd.session.parent"), value: "session.parent", keybind: "session_parent", category: t("cmd.cat.session"), hidden: true, enabled: !!session()?.parentID, onSelect: childHandler((d: DialogContext) => { const pid = session()?.parentID; if (pid) navigate({ type: "session", sessionID: pid }); d.clear() }) },
    { title: t("cmd.session.child_next"), value: "session.child.next", keybind: "session_child_cycle", category: t("cmd.cat.session"), hidden: true, enabled: !!session()?.parentID, onSelect: childHandler((d: DialogContext) => { moveChild(1); d.clear() }) },
    { title: t("cmd.session.child_prev"), value: "session.child.previous", keybind: "session_child_cycle_reverse", category: t("cmd.cat.session"), hidden: true, enabled: !!session()?.parentID, onSelect: childHandler((d: DialogContext) => { moveChild(-1); d.clear() }) },
  ])
}
