import { marked } from "marked";
import { memo } from "preact/compat";
import { useState } from "preact/hooks";
import { html } from "../lib/html.js";
import { t, useLang } from "../i18n/index.js";
import {
  escapeHtml,
  hlLine,
  langFromPath,
  renderHighlightedBlock,
  renderMarkdownToString,
  renderSearchReplace,
} from "../lib/markdown.js";

export type ChatRole = "user" | "assistant" | "tool" | "info" | "warning" | "error";

export interface ChatMsg {
  id: string;
  role: ChatRole;
  text?: string;
  reasoning?: string;
  toolName?: string;
  toolArgs?: string;
}

const ROLE_GLYPH: Record<ChatRole, string> = {
  user: "◇",
  assistant: "◆",
  tool: "▣",
  info: "·",
  warning: "▲",
  error: "✦",
};

export type OnResolve = (kind: string, ...args: unknown[]) => void;

interface ToolCardProps {
  msg: ChatMsg;
}

interface ChatMessageProps {
  msg: ChatMsg;
  streaming?: boolean;
}

interface ModalCardProps {
  accent: string;
  icon: string;
  title: string;
  subtitle?: string;
  children?: unknown;
}

interface ShellModalSpec {
  command: string;
  allowPrefix?: string;
  shellKind?: string;
}

interface ChoiceOption {
  id: string;
  title: string;
  summary?: string;
}

interface ChoiceModalSpec {
  question: string;
  options: ChoiceOption[];
  allowCustom?: boolean;
}

interface PlanModalSpec {
  body?: string;
}

interface EditReviewSpec {
  search?: string;
  replace?: string;
  path?: string;
  remaining: number;
  total: number;
}

interface WorkspaceSpec {
  path: string;
}

interface CheckpointSpec {
  stepId: string;
  title?: string;
  completed?: number;
  total?: number;
}

interface RevisionStep {
  id: string;
  title: string;
  action: string;
  risk?: "low" | "med" | "high";
}

interface RevisionSpec {
  summary?: string;
  reason: string;
  remainingSteps: RevisionStep[];
}

export type PickerActionName =
  | "pick"
  | "delete"
  | "rename"
  | "new"
  | "install"
  | "uninstall"
  | "load-more"
  | "refine"
  | "cancel";

export interface PickerItemSpec {
  id: string;
  title: string;
  subtitle?: string;
  badge?: string;
  meta?: string;
}

export interface PickerModalSpec {
  pickerKind: string;
  title: string;
  query?: string;
  items: PickerItemSpec[];
  actions: PickerActionName[];
  hasMore?: boolean;
  hint?: string;
}

export interface ViewerStep {
  id: string;
  title: string;
  status: "done" | "queued";
}

export interface ViewerModalSpec {
  viewerKind: string;
  title: string;
  body?: string;
  steps?: ViewerStep[];
  meta?: string;
}

interface DiffEntry {
  kind: "context" | "ins" | "del";
  text: string;
}

interface DiffPair {
  left: string | null;
  right: string | null;
  kind: "context" | "change" | "ins" | "del";
}

export function renderMessageBody(text: string | null | undefined) {
  if (!text) return null;
  return html`<div class="md" dangerouslySetInnerHTML=${{ __html: renderMarkdownToString(text) }}></div>`;
}

export function parseToolArgs(raw: string | null | undefined): Record<string, unknown> | null {
  if (!raw) return null;
  try {
    return JSON.parse(raw);
  } catch {
    return null;
  }
}

export function ToolCard({ msg }: ToolCardProps) {
  useLang();
  const args = parseToolArgs(msg.toolArgs);
  const name = msg.toolName ?? "tool";
  // Carbon Code's filesystem tools emit the path in args.path; MCP-bridged
  // ones may differ but most expose a `path` field too. Normalize.
  const path = (args?.path ?? args?.file_path ?? args?.filename) as string | undefined;

  // edit_file (Carbon Code) — search/replace pair → diff view.
  if (
    (name === "edit_file" || name.endsWith("_edit_file")) &&
    args &&
    typeof args.search === "string" &&
    typeof args.replace === "string"
  ) {
    const diffHtml = renderSearchReplace(
      args.search as string,
      args.replace as string,
      path ?? "",
    );
    return html`
      <div class="tool-card">
        <div class="tool-card-head">
          <span class="tool-card-icon">✎</span>
          <span class="tool-card-name">edit_file</span>
          ${path ? html`<code class="tool-card-path">${path}</code>` : null}
        </div>
        <div dangerouslySetInnerHTML=${{ __html: diffHtml }}></div>
        ${msg.text ? html`<div class="tool-card-result">${msg.text}</div>` : null}
      </div>
    `;
  }

  // write_file — show new content as a code block with path-derived lang.
  if (
    (name === "write_file" || name.endsWith("_write_file")) &&
    args &&
    typeof args.content === "string"
  ) {
    const lang = langFromPath(path);
    return html`
      <div class="tool-card">
        <div class="tool-card-head">
          <span class="tool-card-icon">+</span>
          <span class="tool-card-name">write_file</span>
          ${path ? html`<code class="tool-card-path">${path}</code>` : null}
          ${lang ? html`<span class="pill">${lang}</span>` : null}
        </div>
        <div dangerouslySetInnerHTML=${{ __html: renderHighlightedBlock(args.content as string, lang) }}></div>
        ${msg.text ? html`<div class="tool-card-result">${msg.text}</div>` : null}
      </div>
    `;
  }

  // read_file / list_files — content lands in msg.text.
  if (name === "read_file" || name.endsWith("_read_file") || name === "filesystem_read_file") {
    const lang = langFromPath(path);
    return html`
      <div class="tool-card">
        <div class="tool-card-head">
          <span class="tool-card-icon">▤</span>
          <span class="tool-card-name">read_file</span>
          ${path ? html`<code class="tool-card-path">${path}</code>` : null}
          ${lang ? html`<span class="pill">${lang}</span>` : null}
        </div>
        <div dangerouslySetInnerHTML=${{ __html: renderHighlightedBlock(msg.text ?? "", lang) }}></div>
      </div>
    `;
  }

  // run_command / run_background — terminal-style.
  if (name === "run_command" || name === "run_background") {
    const cmd = args?.command;
    return html`
      <div class="tool-card">
        <div class="tool-card-head">
          <span class="tool-card-icon">⚡</span>
          <span class="tool-card-name">${name === "run_background" ? "run_background" : "run_command"}</span>
        </div>
        ${
          cmd
            ? html`<pre class="tool-card-cmd"><span class="tool-card-prompt">$</span> <code>${cmd}</code></pre>`
            : null
        }
        ${msg.text ? html`<pre class="tool-card-output">${msg.text}</pre>` : null}
      </div>
    `;
  }

  // list_files / file_exists / delete_file — show args + result inline.
  if (
    name === "list_files" ||
    name === "file_exists" ||
    name === "delete_file" ||
    name === "create_directory" ||
    name === "delete_directory" ||
    name.endsWith("_list_files")
  ) {
    return html`
      <div class="tool-card">
        <div class="tool-card-head">
          <span class="tool-card-icon">▣</span>
          <span class="tool-card-name">${name}</span>
          ${path ? html`<code class="tool-card-path">${path}</code>` : null}
        </div>
        <pre class="tool-card-output">${msg.text}</pre>
      </div>
    `;
  }

  // Default — keep the legacy compact box but add an args preview when
  // present so MCP-bridged tools still surface something readable.
  return html`
    <div class="tool-card">
      <div class="tool-card-head">
        <span class="tool-card-icon">▣</span>
        <span class="tool-card-name">${name}</span>
      </div>
      ${
        args
          ? html`<details class="tool-card-args"><summary>${t("modal.arguments")}</summary><pre>${escapeHtml(JSON.stringify(args, null, 2))}</pre></details>`
          : null
      }
      <pre class="tool-card-output">${msg.text}</pre>
    </div>
  `;
}

// memo() short-circuits re-renders when shallow props are unchanged.
// Historical messages keep stable msg references across deltas, so the
// O(N) marked.parse + hljs work that used to fire per assistant_delta
// now only runs on truly new messages and the live streaming bubble.
export const ChatMessage = memo(function ChatMessage({ msg, streaming }: ChatMessageProps) {
  const role = msg.role;
  const glyph = ROLE_GLYPH[role as ChatRole] ?? "·";
  if (role === "tool") {
    return html`
      <div class="chat-msg tool">
        <div class="glyph">${glyph}</div>
        <${ToolCard} msg=${msg} />
      </div>
    `;
  }
  return html`
    <div class="chat-msg ${role}">
      <div class="glyph">${glyph}</div>
      <div class="body">
        ${msg.reasoning ? html`<div class="reasoning">${msg.reasoning}</div>` : null}
        ${renderMessageBody(msg.text)}
        ${streaming ? html`<span class="chat-streaming-cursor"></span>` : null}
      </div>
    </div>
  `;
});

//
// Each component renders a card matching the TUI's ModalCard accent
// palette: red for shell (run-now), magenta for choice (branching),
// cyan for plan (decision), green for edits. onResolve pushes to the
// server; the SSE channel will echo back a modal-down that clears the
// local state — both surfaces stay in lockstep without polling.

export function ModalCard({ accent, icon, title, subtitle, children }: ModalCardProps) {
  return html`
    <div class="modal-card" style=${`border-left-color: ${accent};`}>
      <div class="modal-card-head">
        <span class="modal-card-icon" style=${`color: ${accent};`}>${icon}</span>
        <div>
          <div class="modal-card-title">${title}</div>
          ${subtitle ? html`<div class="modal-card-subtitle">${subtitle}</div>` : null}
        </div>
      </div>
      ${children}
    </div>
  `;
}

export function ShellModal({ modal, onResolve }: { modal: ShellModalSpec; onResolve: OnResolve }) {
  useLang();
  const isBg = modal.shellKind === "run_background";
  return html`
    <${ModalCard}
      accent="#f87171"
      icon=${isBg ? "⏱" : "⚡"}
      title=${isBg ? t("modal.shellBgTitle") : t("modal.shellTitle")}
      subtitle=${
        isBg ? t("modal.shellBgSubtitle") : t("modal.shellSubtitle")
      }
    >
      <div class="modal-cmd"><span class="modal-cmd-prompt">$</span> <code>${modal.command}</code></div>
      <div class="modal-actions">
        <button class="primary" onClick=${() => onResolve("shell", "run_once")}>${t("modal.runOnce")}</button>
        <button onClick=${() => onResolve("shell", "always_allow")}>${t("modal.alwaysAllow", { prefix: modal.allowPrefix ?? "" })}</button>
        <button class="danger" onClick=${() => onResolve("shell", "deny")}>${t("modal.deny")}</button>
      </div>
    <//>
  `;
}

export function ChoiceModal({ modal, onResolve }: { modal: ChoiceModalSpec; onResolve: OnResolve }) {
  useLang();
  const [custom, setCustom] = useState("");
  const [showCustom, setShowCustom] = useState(false);
  return html`
    <${ModalCard} accent="#f0abfc" icon="🔀" title=${t("modal.choiceTitle")} subtitle=${modal.question}>
      ${modal.options.map(
        (opt: ChoiceOption) => html`
        <button
          key=${opt.id}
          class="modal-choice-row"
          onClick=${() => onResolve("choice", { kind: "pick", optionId: opt.id })}
        >
          <span class="modal-choice-id">${opt.id}</span>
          <span class="modal-choice-title">${opt.title}</span>
          ${opt.summary ? html`<span class="modal-choice-summary">${opt.summary}</span>` : null}
        </button>
      `,
      )}
      ${
        modal.allowCustom
          ? showCustom
            ? html`
            <div class="modal-custom">
              <textarea
                placeholder=${t("modal.typePlaceholder")}
                rows="2"
                value=${custom}
                onInput=${(e: Event) => setCustom((e.target as HTMLTextAreaElement).value)}
              ></textarea>
              <div class="modal-actions">
                <button class="primary" onClick=${() => onResolve("choice", { kind: "custom", text: custom })} disabled=${!custom.trim()}>${t("modal.send")}</button>
                <button onClick=${() => {
                  setShowCustom(false);
                  setCustom("");
                }}>${t("common.back")}</button>
              </div>
            </div>
          `
            : html`
            <button class="modal-choice-row" onClick=${() => setShowCustom(true)}>
              <span class="modal-choice-id">·</span>
              <span class="modal-choice-title">${t("modal.typeOwn")}</span>
              <span class="modal-choice-summary">${t("modal.typeOwnSummary")}</span>
            </button>
          `
          : null
      }
      <button class="modal-choice-row modal-choice-cancel" onClick=${() => onResolve("choice", { kind: "cancel" })}>
        <span class="modal-choice-id">×</span>
        <span class="modal-choice-title">${t("modal.cancel")}</span>
        <span class="modal-choice-summary">${t("modal.cancelSummary")}</span>
      </button>
    <//>
  `;
}

export function PlanModal({ modal, onResolve }: { modal: PlanModalSpec; onResolve: OnResolve }) {
  useLang();
  const [feedback, setFeedback] = useState("");
  const [stage, setStage] = useState<"approve" | "refine" | null>(null);
  const send = () => onResolve("plan", stage, feedback);
  return html`
    <${ModalCard} accent="#67e8f9" icon="◆" title=${t("modal.planTitle")} subtitle=${t("modal.planSubtitle")}>
      <div class="md modal-plan-body" dangerouslySetInnerHTML=${{ __html: marked.parse(modal.body || "") }}></div>
      ${
        stage
          ? html`
          <textarea
            placeholder=${
              stage === "approve"
                ? t("modal.approveInstructions")
                : t("modal.refinePlaceholder")
            }
            rows="3"
            value=${feedback}
            onInput=${(e: Event) => setFeedback((e.target as HTMLTextAreaElement).value)}
          ></textarea>
          <div class="modal-actions">
            <button class="primary" onClick=${send}>${stage === "approve" ? t("modal.approve") : t("modal.sendRefinement")}</button>
            <button onClick=${() => {
              setStage(null);
              setFeedback("");
            }}>${t("common.back")}</button>
          </div>
        `
          : html`
          <div class="modal-actions">
            <button class="primary" onClick=${() => setStage("approve")}>${t("modal.approve")}</button>
            <button onClick=${() => setStage("refine")}>${t("modal.refine")}</button>
            <button class="danger" onClick=${() => onResolve("plan", "cancel")}>${t("modal.cancel")}</button>
          </div>
        `
      }
    <//>
  `;
}

// Line-level LCS diff. Returns an ordered list of rows; "context" rows
// appear on both sides, "del" only on the left (red), "ins" only on the
// right (green). Adjacent del/ins are paired into one row downstream so
// the change reads "old → new" left-to-right like a git side-by-side.
function lineDiff(aLines: string[], bLines: string[]): DiffEntry[] {
  const m = aLines.length;
  const n = bLines.length;
  const dp: number[][] = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0));
  for (let i = 1; i <= m; i++) {
    for (let j = 1; j <= n; j++) {
      if (aLines[i - 1] === bLines[j - 1]) dp[i]![j] = dp[i - 1]![j - 1]! + 1;
      else dp[i]![j] = Math.max(dp[i - 1]![j]!, dp[i]![j - 1]!);
    }
  }
  const out: DiffEntry[] = [];
  let i = m;
  let j = n;
  while (i > 0 || j > 0) {
    if (i > 0 && j > 0 && aLines[i - 1] === bLines[j - 1]) {
      out.push({ kind: "context", text: aLines[i - 1]! });
      i--;
      j--;
    } else if (j > 0 && (i === 0 || dp[i]![j - 1]! >= dp[i - 1]![j]!)) {
      out.push({ kind: "ins", text: bLines[j - 1]! });
      j--;
    } else {
      out.push({ kind: "del", text: aLines[i - 1]! });
      i--;
    }
  }
  return out.reverse();
}

// Pair del/ins runs into side-by-side rows. A run of consecutive dels
// followed by a run of inss collapses into rows of (del[k], ins[k]) so
// the modified line lines up across the gutter; surplus on either side
// produces rows with the opposite cell empty.
function pairDiffRows(diff: DiffEntry[]): DiffPair[] {
  const rows: DiffPair[] = [];
  let k = 0;
  while (k < diff.length) {
    const entry = diff[k]!;
    if (entry.kind === "context") {
      rows.push({ left: entry.text, right: entry.text, kind: "context" });
      k++;
      continue;
    }
    const dels: string[] = [];
    const inss: string[] = [];
    while (k < diff.length && diff[k]!.kind === "del") {
      dels.push(diff[k]!.text);
      k++;
    }
    while (k < diff.length && diff[k]!.kind === "ins") {
      inss.push(diff[k]!.text);
      k++;
    }
    const pairs = Math.max(dels.length, inss.length);
    for (let p = 0; p < pairs; p++) {
      const dp = dels[p];
      const ip = inss[p];
      rows.push({
        left: dp ?? null,
        right: ip ?? null,
        kind: dp != null && ip != null ? "change" : dp != null ? "del" : "ins",
      });
    }
  }
  return rows;
}

export function EditReviewModal({ modal, onResolve }: { modal: EditReviewSpec; onResolve: OnResolve }) {
  useLang();
  const search = modal.search ?? "";
  const replace = modal.replace ?? "";
  const lang = langFromPath(modal.path);
  const aLines = search.split("\n");
  const bLines = replace.split("\n");
  const rows = pairDiffRows(lineDiff(aLines, bLines));

  return html`
    <${ModalCard}
      accent="#86efac"
      icon="◆"
      title=${t("modal.editTitle")}
      subtitle=${t("modal.editSubtitle", { path: modal.path ?? "", remaining: modal.remaining, total: modal.total })}
    >
      <div class="edit-diff-wrap">
        <div class="edit-diff-head">
          <div class="edit-diff-side edit-diff-side-old">
            <span class="edit-diff-marker">−</span> ${t("modal.before")}
          </div>
          <div class="edit-diff-side edit-diff-side-new">
            <span class="edit-diff-marker">+</span> ${t("modal.after")}
          </div>
        </div>
        <div class="edit-diff-body">
          ${rows.map(
            (row, i) => html`
            <div key=${i} class=${`edit-diff-row edit-diff-row-${row.kind}`}>
              <div class="edit-diff-cell edit-diff-cell-old">
                ${
                  row.left != null
                    ? html`<span
                        class="edit-diff-line"
                        dangerouslySetInnerHTML=${{ __html: hlLine(row.left, lang) || "&nbsp;" }}
                      ></span>`
                    : html`<span class="edit-diff-empty">&nbsp;</span>`
                }
              </div>
              <div class="edit-diff-cell edit-diff-cell-new">
                ${
                  row.right != null
                    ? html`<span
                        class="edit-diff-line"
                        dangerouslySetInnerHTML=${{ __html: hlLine(row.right, lang) || "&nbsp;" }}
                      ></span>`
                    : html`<span class="edit-diff-empty">&nbsp;</span>`
                }
              </div>
            </div>
          `,
          )}
        </div>
      </div>
      <div class="modal-actions">
        <button class="primary" onClick=${() => onResolve("edit-review", "apply")}>${t("chat.confirmBtn")}</button>
        <button onClick=${() => onResolve("edit-review", "reject")}>${t("chat.rejectBtn")}</button>
        <button onClick=${() => onResolve("edit-review", "apply-rest-of-turn")}>${t("chat.applyRestBtn")}</button>
        <button onClick=${() => onResolve("edit-review", "flip-to-auto")}>${t("chat.flipAutoBtn")}</button>
      </div>
    <//>
  `;
}

export function WorkspaceModal({ modal, onResolve }: { modal: WorkspaceSpec; onResolve: OnResolve }) {
  useLang();
  return html`
    <${ModalCard}
      accent="#fbbf24"
      icon="◇"
      title=${t("modal.workspaceTitle")}
      subtitle=${t("modal.workspaceSubtitle")}
    >
      <div class="modal-cmd"><span class="modal-cmd-prompt">→</span> <code>${modal.path}</code></div>
      <div class="modal-actions">
        <button class="primary" onClick=${() => onResolve("workspace", "switch")}>${t("modal.switchBtn")}</button>
        <button class="danger" onClick=${() => onResolve("workspace", "deny")}>${t("modal.denyBtn")}</button>
      </div>
    <//>
  `;
}

export function CheckpointModal({ modal, onResolve }: { modal: CheckpointSpec; onResolve: OnResolve }) {
  useLang();
  const [reviseText, setReviseText] = useState("");
  const [staged, setStaged] = useState(false);
  const label = modal.title ? `${modal.stepId} · ${modal.title}` : modal.stepId;
  const counter = (modal.total ?? 0) > 0 ? ` (${modal.completed}/${modal.total})` : "";
  return html`
    <${ModalCard}
      accent="#a5f3fc"
      icon="✓"
      title=${t("modal.stepComplete", { counter })}
      subtitle=${label}
    >
      ${
        staged
          ? html`
          <textarea
            placeholder=${t("modal.revisePlaceholder")}
            rows="3"
            value=${reviseText}
            onInput=${(e: Event) => setReviseText((e.target as HTMLTextAreaElement).value)}
          ></textarea>
          <div class="modal-actions">
            <button class="primary" onClick=${() => onResolve("checkpoint", "revise", reviseText)}>${t("modal.sendRevision")}</button>
            <button onClick=${() => {
              setStaged(false);
              setReviseText("");
            }}>${t("common.back")}</button>
          </div>
        `
          : html`
          <div class="modal-actions">
            <button class="primary" onClick=${() => onResolve("checkpoint", "continue")}>${t("modal.continueBtn")}</button>
            <button onClick=${() => setStaged(true)}>${t("modal.reviseBtn")}</button>
            <button class="danger" onClick=${() => onResolve("checkpoint", "stop")}>${t("modal.stopBtn")}</button>
          </div>
        `
      }
    <//>
  `;
}

export function PickerModal({
  modal,
  onResolve,
}: {
  modal: PickerModalSpec;
  onResolve: OnResolve;
}) {
  useLang();
  const [selectedId, setSelectedId] = useState<string | null>(modal.items[0]?.id ?? null);
  const [query, setQuery] = useState(modal.query ?? "");
  const [renameTarget, setRenameTarget] = useState<string | null>(null);
  const [renameText, setRenameText] = useState("");
  const [showNew, setShowNew] = useState(false);
  const [newText, setNewText] = useState("");

  const has = (a: PickerActionName) => modal.actions.includes(a);
  const selected = modal.items.find((i) => i.id === selectedId) ?? null;

  const submitRefine = (next: string) => {
    setQuery(next);
    if (has("refine")) onResolve("picker", { action: "refine", query: next });
  };

  const startRename = (id: string) => {
    const item = modal.items.find((i) => i.id === id);
    if (!item) return;
    setRenameTarget(id);
    setRenameText(item.title);
  };

  const sendRename = () => {
    if (!renameTarget || !renameText.trim()) return;
    onResolve("picker", { action: "rename", id: renameTarget, text: renameText });
    setRenameTarget(null);
    setRenameText("");
  };

  const sendNew = () => {
    onResolve("picker", newText.trim() ? { action: "new", text: newText } : { action: "new" });
    setShowNew(false);
    setNewText("");
  };

  return html`
    <${ModalCard}
      accent="#fcd34d"
      icon="≡"
      title=${modal.title}
      subtitle=${modal.hint}
    >
      ${
        has("refine")
          ? html`<input
              class="modal-picker-search"
              type="search"
              placeholder=${t("modal.pickerFilter")}
              value=${query}
              onInput=${(e: Event) => submitRefine((e.target as HTMLInputElement).value)}
            />`
          : null
      }
      <div class="modal-picker-list">
        ${
          modal.items.length === 0
            ? html`<div class="modal-picker-empty">${t("modal.pickerEmpty")}</div>`
            : modal.items.map(
                (it) => html`
                  <button
                    key=${it.id}
                    class=${`modal-picker-row${it.id === selectedId ? " selected" : ""}`}
                    onClick=${() => setSelectedId(it.id)}
                    onDblClick=${() => has("pick") && onResolve("picker", { action: "pick", id: it.id })}
                  >
                    <span class="modal-picker-title">${it.title}</span>
                    ${it.badge ? html`<span class="modal-picker-badge">${it.badge}</span>` : null}
                    ${it.subtitle ? html`<span class="modal-picker-subtitle">${it.subtitle}</span>` : null}
                    ${it.meta ? html`<span class="modal-picker-meta">${it.meta}</span>` : null}
                  </button>
                `,
              )
        }
      </div>
      ${
        modal.hasMore && has("load-more")
          ? html`<button
              class="modal-picker-more"
              onClick=${() => onResolve("picker", { action: "load-more" })}
            >${t("modal.pickerLoadMore")}</button>`
          : null
      }
      ${
        renameTarget
          ? html`
            <div class="modal-picker-form">
              <input
                type="text"
                value=${renameText}
                onInput=${(e: Event) => setRenameText((e.target as HTMLInputElement).value)}
              />
              <div class="modal-actions">
                <button class="primary" onClick=${sendRename} disabled=${!renameText.trim()}>${t("common.save")}</button>
                <button onClick=${() => setRenameTarget(null)}>${t("common.back")}</button>
              </div>
            </div>
          `
          : showNew
            ? html`
              <div class="modal-picker-form">
                <input
                  type="text"
                  placeholder=${t("modal.pickerNewPlaceholder")}
                  value=${newText}
                  onInput=${(e: Event) => setNewText((e.target as HTMLInputElement).value)}
                />
                <div class="modal-actions">
                  <button class="primary" onClick=${sendNew}>${t("common.add")}</button>
                  <button onClick=${() => setShowNew(false)}>${t("common.back")}</button>
                </div>
              </div>
            `
            : html`
              <div class="modal-actions">
                ${
                  has("pick") && selected
                    ? html`<button
                        class="primary"
                        onClick=${() => onResolve("picker", { action: "pick", id: selected.id })}
                      >${t("modal.pickerPick")}</button>`
                    : null
                }
                ${
                  has("install") && selected
                    ? html`<button
                        class="primary"
                        onClick=${() => onResolve("picker", { action: "install", id: selected.id })}
                      >${t("modal.pickerInstall")}</button>`
                    : null
                }
                ${
                  has("uninstall") && selected
                    ? html`<button
                        onClick=${() => onResolve("picker", { action: "uninstall", id: selected.id })}
                      >${t("modal.pickerUninstall")}</button>`
                    : null
                }
                ${
                  has("rename") && selected
                    ? html`<button onClick=${() => startRename(selected.id)}>${t("modal.pickerRename")}</button>`
                    : null
                }
                ${
                  has("delete") && selected
                    ? html`<button
                        class="danger"
                        onClick=${() => onResolve("picker", { action: "delete", id: selected.id })}
                      >${t("common.delete")}</button>`
                    : null
                }
                ${
                  has("new")
                    ? html`<button onClick=${() => setShowNew(true)}>${t("modal.pickerNew")}</button>`
                    : null
                }
                <button onClick=${() => onResolve("picker", { action: "cancel" })}>${t("modal.cancel")}</button>
              </div>
            `
      }
    <//>
  `;
}

export function ViewerModal({
  modal,
  onResolve,
}: {
  modal: ViewerModalSpec;
  onResolve: OnResolve;
}) {
  useLang();
  return html`
    <${ModalCard}
      accent="#67e8f9"
      icon="◇"
      title=${modal.title}
      subtitle=${modal.meta}
    >
      ${
        modal.steps && modal.steps.length > 0
          ? html`
            <ol class="modal-viewer-steps">
              ${modal.steps.map(
                (s: ViewerStep) => html`
                  <li key=${s.id} class=${`modal-viewer-step modal-viewer-step-${s.status}`}>
                    <span class="modal-viewer-step-mark">${s.status === "done" ? "✓" : "·"}</span>
                    <span class="modal-viewer-step-title">${s.title}</span>
                  </li>
                `,
              )}
            </ol>
          `
          : null
      }
      ${
        modal.body
          ? html`<div class="md modal-viewer-body" dangerouslySetInnerHTML=${{ __html: marked.parse(modal.body) }}></div>`
          : null
      }
      <div class="modal-actions">
        <button onClick=${() => onResolve("viewer", { action: "close" })}>${t("modal.viewerClose")}</button>
      </div>
    <//>
  `;
}

export function RevisionModal({ modal, onResolve }: { modal: RevisionSpec; onResolve: OnResolve }) {
  useLang();
  const riskColor = (r: string | undefined) =>
    r === "high" ? "#f87171" : r === "med" ? "#fbbf24" : r === "low" ? "#86efac" : "#9ca3af";
  return html`
    <${ModalCard}
      accent="#c4b5fd"
      icon="✎"
      title=${t("modal.revisionTitle")}
      subtitle=${modal.summary || modal.reason}
    >
      <div class="modal-revise-reason">${modal.reason}</div>
      <ol class="modal-revise-steps">
        ${modal.remainingSteps.map(
          (s: RevisionStep) => html`
            <li key=${s.id}>
              <span class="modal-revise-dot" style=${`background:${riskColor(s.risk)}`}></span>
              <span class="modal-revise-id">${s.id}</span>
              <span class="modal-revise-title">${s.title}</span>
              <span class="modal-revise-action">${s.action}</span>
            </li>
          `,
        )}
      </ol>
      <div class="modal-actions">
        <button class="primary" onClick=${() => onResolve("revision", "accept")}>${t("modal.accept")}</button>
        <button class="danger" onClick=${() => onResolve("revision", "reject")}>${t("modal.reject")}</button>
      </div>
    <//>
  `;
}
