import type { ApprovalRequest, ToolCall } from "#cli/core/model";
import type { PropType } from "vue";
import { normalizeText } from "#cli/core/utils";
import { t } from "#cli/i18n";
import { TBox, textCellWidth, TText, TView, wrapByCells } from "@simon_he/vue-tui";
import { defineComponent, h, ref, watch } from "vue";
import { useUiTheme } from "../theme";
import { clamp } from "../utils/math";

function stripAutoAppendedReasonDetail(reason: string): string {
  const text = normalizeText(reason);
  const idx = text.search(/\s(?:Args|Command|Description|命令|说明)[：:]\s/i);
  if (idx <= 0) return text;
  return text.slice(0, idx).trimEnd();
}

function fitApprovalButtonLabels(availableCells: number): {
  approve: string;
  always: string;
  deny: string;
} {
  const gap = availableCells < 34 ? 1 : 2;
  const n = 3;
  const maxTotal = Math.max(0, Math.floor(availableCells));

  const totalW = (approve: string, always: string, deny: string) => {
    const labels = [approve, always, deny];
    const widths = labels.map((l) => textCellWidth(`[ ${l} ]`));
    return widths.reduce((sum, w) => sum + w, 0) + gap * Math.max(0, n - 1);
  };

  const variants = [
    {
      approve: t("approvalDialog.buttons.approve"),
      always: t("approvalDialog.buttons.alwaysApprove"),
      deny: t("approvalDialog.buttons.deny"),
    },
    {
      approve: t("approvalDialog.buttons.approve"),
      always: t("approvalDialog.buttons.always"),
      deny: t("approvalDialog.buttons.deny"),
    },
    {
      approve: t("approvalDialog.buttons.ok"),
      always: t("approvalDialog.buttons.always"),
      deny: t("approvalDialog.buttons.no"),
    },
  ] as const;

  for (const v of variants) {
    if (totalW(v.approve, v.always, v.deny) <= maxTotal) return v;
  }

  return variants[variants.length - 1]!;
}

function extractBashPreview(
  call: ToolCall | null | undefined,
): { command?: string; description?: string } | null {
  if (!call) return null;
  const args = call.arguments as any;
  const command = typeof args?.command === "string" ? args.command : undefined;
  const description = typeof args?.description === "string" ? args.description : undefined;
  if (!command && !description) return null;
  return { command, description };
}

function extractBashPreviewFromArgs(
  args: unknown,
): { command?: string; description?: string } | null {
  if (!args || typeof args !== "object" || Array.isArray(args)) return null;
  const obj = args as any;
  const command = typeof obj.command === "string" ? obj.command : undefined;
  const description = typeof obj.description === "string" ? obj.description : undefined;
  if (!command && !description) return null;
  return { command, description };
}

export const DimSdkApprovalDialog = defineComponent({
  name: "DimSdkApprovalDialog",
  props: {
    modelValue: { type: Boolean, required: true },
    request: { type: Object as PropType<ApprovalRequest>, required: true },
    call: {
      type: Object as PropType<ToolCall | null>,
      required: false,
      default: null,
    },
    x: { type: Number, required: true },
    y: { type: Number, required: true },
    w: { type: Number, required: true },
    h: { type: Number, required: true },
  },
  emits: {
    "update:modelValue": (_v: boolean) => true,
    approve: () => true,
    alwaysApprove: () => true,
    deny: () => true,
  },
  setup(props, { emit }) {
    const ui = useUiTheme();
    const keyHandledFlag = "__dimsdkApprovalDialogHandled";
    const reasonScrollOffset = ref(0);
    const selectedButtonIndex = ref(0);
    const lastRequestId = ref<string | null>(null);
    const triggerAction = (action: "approve" | "alwaysApprove" | "deny") => {
      if (action === "approve") emit("approve");
      else if (action === "alwaysApprove") emit("alwaysApprove");
      else emit("deny");
    };

    watch(
      [() => props.modelValue, () => props.request.id],
      ([open, id]) => {
        if (!open) return;
        if (lastRequestId.value !== id) reasonScrollOffset.value = 0;
        lastRequestId.value = id;
        selectedButtonIndex.value = 0;
      },
      { immediate: true },
    );

    watch(
      () => props.modelValue,
      (open, prev) => {
        if (!open && prev) selectedButtonIndex.value = 0;
      },
      { immediate: true },
    );

    const computeReasonViewport = () => {
      const w = Math.max(0, props.w - 4); // border + padding (TBox: border=1, padding=1)
      const innerH = Math.max(0, props.h - 4); // border + padding
      const footerH = 1; // buttons row
      const contentH = Math.max(0, innerH - footerH);

      let y = 1;
      if (contentH - y >= 4) y += 1;

      const req = props.request;
      const reqArgs = req.args;
      const bash = extractBashPreview(props.call) ?? extractBashPreviewFromArgs(reqArgs);

      const reason = stripAutoAppendedReasonDetail(req.reason).trim();
      const reasonLines = reason ? wrapByCells(reason, Math.max(1, w)) : [];

      const remainingH = Math.max(0, contentH - y);
      const bashH = bash?.command ? 1 : 0;
      const reserve = bashH;
      const reasonH =
        reasonLines.length > 0
          ? clamp(Math.max(1, remainingH - reserve), 1, Math.max(1, remainingH))
          : 0;
      const maxOffset = Math.max(0, reasonLines.length - reasonH);

      return { reasonH, maxOffset };
    };

    return () => {
      if (!props.modelValue) return null;

      const boxW = Math.max(0, Math.floor(props.w));
      const boxH = Math.max(0, Math.floor(props.h));
      const innerW = Math.max(0, boxW - 4); // border + padding
      const innerH = Math.max(0, boxH - 4); // border + padding
      const footerH = 1;
      const contentH = Math.max(0, innerH - footerH);

      const labels = fitApprovalButtonLabels(innerW);
      const dialogBg = ui.token("dialog.chrome")?.bg ?? ui.token("bg.window")?.bg;
      const dialogBgStyle = dialogBg ? { bg: dialogBg } : undefined;
      const bodyBg = ui.style(["dialog.chrome"]);
      const textPrimary = ui.style(["dialog.chrome", "text.primary"]);
      const textMuted = ui.style(["dialog.chrome", "text.muted"]);
      const highlight = ui.style(["select.item"]);
      const activeButtonBg =
        (ui.token("border.default") as any)?.fg ??
        (ui.token("bg.chrome") as any)?.bg ??
        (highlight as any)?.bg;
      const approveStyleRaw = ui.style(["status.success"]);
      const alwaysStyleRaw = ui.style(["status.warning"]);
      const denyStyleRaw = ui.style(["status.danger"]);
      const stripButtonBg = (style: any) => {
        if (!style) return style;
        const rest = { ...(style as any) };
        delete rest.bg;
        delete rest.inverse;
        return rest;
      };
      const buttonBgStyle = dialogBg ? { bg: dialogBg } : undefined;
      const approveStyle = {
        ...stripButtonBg(approveStyleRaw),
        ...buttonBgStyle,
      };
      const alwaysStyle = { ...stripButtonBg(alwaysStyleRaw), ...buttonBgStyle };
      const denyStyle = { ...stripButtonBg(denyStyleRaw), ...buttonBgStyle };

      const req = props.request;
      const reqArgs = req.args;
      const bash = extractBashPreview(props.call) ?? extractBashPreviewFromArgs(reqArgs);
      const toolSuffix = bash?.description
        ? t("approvalDialog.toolLabelSuffix", {
            description: normalizeText(bash.description),
          })
        : "";
      const riskLabel = String(req.riskLevel ?? t("chatStatus.riskFallback")).trim();
      const baseToolLabel = t("approvalDialog.toolLabel", {
        tool: req.tool,
        suffix: toolSuffix,
      });
      const riskLower = riskLabel.toLowerCase();
      const riskTitleStyle =
        riskLower.includes("critical") || riskLower.includes("high") || riskLower.includes("heigh")
          ? ui.style(["status.danger"])
          : riskLower.includes("medium") ||
              riskLower.includes("meduim") ||
              riskLower.includes("med") ||
              riskLower.includes("mid")
            ? ui.style(["status.warning"])
            : riskLower.includes("low")
              ? ui.style(["status.success"])
              : ui.style(["dialog.chrome"]);
      const chromeStyle = ui.style(["dialog.chrome"]);
      const boxStyle = {
        ...chromeStyle,
        fg: (riskTitleStyle as any)?.fg ?? chromeStyle.fg,
        bold: true,
      };

      const nodes: any[] = [
        h(TText, {
          x: 0,
          y: 0,
          w: innerW,
          h: innerH,
          value: "",
          style: bodyBg,
        }),
        h(TText, {
          x: 0,
          y: 0,
          w: innerW,
          value: baseToolLabel,
          style: { ...textPrimary, bold: true },
        }),
      ];

      let y = 1;
      if (contentH - y >= 4) y += 1;

      if (y < contentH) {
        const reasonText = stripAutoAppendedReasonDetail(req.reason).trim();
        const reasonLines = reasonText ? wrapByCells(reasonText, Math.max(1, innerW)) : [];

        if (reasonLines.length > 0) {
          const remainingH = Math.max(0, contentH - y);
          const bashH = bash?.command ? 1 : 0;
          const reserve = bashH;
          const reasonH = clamp(Math.max(1, remainingH - reserve), 1, Math.max(1, remainingH));
          const maxOffset = Math.max(0, reasonLines.length - reasonH);
          const offset = clamp(reasonScrollOffset.value, 0, maxOffset);
          const visible = reasonLines.slice(offset, offset + reasonH).join("\n");

          nodes.push(
            h(TText, {
              x: 0,
              y,
              w: innerW,
              h: reasonH,
              value: visible,
              style: textMuted,
            }),
          );
          if (innerW > 0 && reasonH > 0) {
            if (offset > 0)
              nodes.push(
                h(TText, {
                  x: innerW - 1,
                  y,
                  w: 1,
                  value: "↑",
                  style: textMuted,
                }),
              );
            if (offset + reasonH < reasonLines.length)
              nodes.push(
                h(TText, {
                  x: innerW - 1,
                  y: y + reasonH - 1,
                  w: 1,
                  value: "↓",
                  style: textMuted,
                }),
              );
          }

          y += reasonH;
        }
      }

      if (bash?.command && y < contentH) {
        const label = t("approvalDialog.commandLabel");
        const labelW = textCellWidth(label);
        const availableW = Math.max(0, innerW - labelW);
        const commandText = normalizeText(bash.command);
        const commandLine = availableW > 0 ? (wrapByCells(commandText, availableW)[0] ?? "") : "";
        nodes.push(h(TText, { x: 0, y, w: innerW, value: label, style: textPrimary }));
        if (availableW > 0) {
          nodes.push(
            h(TText, {
              x: labelW,
              y,
              w: availableW,
              value: commandLine,
              style: ui.style(["code.inline"], dialogBgStyle),
            }),
          );
        }
        y += 1;
      }

      const buttons = [
        { label: labels.approve, value: "approve" as const },
        { label: labels.always, value: "alwaysApprove" as const },
        { label: labels.deny, value: "deny" as const },
      ];
      const btnY = innerH - 1;
      if (btnY >= 0) {
        const gap = innerW < 34 ? 1 : 2;
        let x = 0;
        for (let i = 0; i < buttons.length; i++) {
          const btn = buttons[i]!;
          const text = `[ ${btn.label} ]`;
          const w = textCellWidth(text);
          if (x + w > innerW) break;
          const baseStyle = i === 0 ? approveStyle : i === 1 ? alwaysStyle : denyStyle;
          const style =
            i === selectedButtonIndex.value
              ? {
                  ...baseStyle,
                  ...(activeButtonBg ? { bg: activeButtonBg } : { inverse: true }),
                  bold: true,
                  dim: false,
                }
              : baseStyle;
          nodes.push(h(TText, { x, y: btnY, w, value: text, style }));
          nodes.push(
            h(TView as any, {
              x,
              y: btnY,
              w,
              h: 1,
              zIndex: 5,
              focusable: false,
              onClick: (e: any) => {
                e?.stopPropagation?.();
                selectedButtonIndex.value = i;
                triggerAction(btn.value);
              },
            }),
          );
          x += w + gap;
        }
      }

      return h(
        TView as any,
        {
          x: Math.max(0, Math.floor(props.x)),
          y: Math.max(0, Math.floor(props.y)),
          w: boxW,
          h: boxH,
          zIndex: 1600,
          focusable: true,
          autoFocus: true,
          onClick: (e: any) => {
            e?.stopPropagation?.();
          },
          onKeydownCapture: (e: any) => {
            if (e?.key === "Enter") {
              (e as any)[keyHandledFlag] = true;
            }
            if (e?.key === "Escape") {
              e.preventDefault?.();
              e.stopPropagation?.();
              triggerAction("deny");
              return;
            }
            // Block printable keys to avoid IME/layout issues (no text input here).
            if (
              typeof e?.key === "string" &&
              e.key.length === 1 &&
              !e.ctrlKey &&
              !e.metaKey &&
              !e.altKey
            ) {
              e.preventDefault?.();
              e.stopPropagation?.();
            }
          },
          onKeydown: (e: any) => {
            if (e?.key === "ArrowLeft" || e?.key === "ArrowRight") {
              const dir = e.key === "ArrowLeft" ? -1 : 1;
              const len = Math.max(1, 3);
              selectedButtonIndex.value = (selectedButtonIndex.value + dir + len) % len;
              e.preventDefault?.();
              e.stopPropagation?.();
              return;
            }
            if (e?.key === "Enter") {
              const idx = clamp(selectedButtonIndex.value, 0, 2);
              const btn = (["approve", "alwaysApprove", "deny"] as const)[idx];
              if (btn) triggerAction(btn);
              return;
            }

            const { reasonH, maxOffset } = computeReasonViewport();
            if (maxOffset <= 0) return;

            const prev = reasonScrollOffset.value;
            let next = prev;

            if (e?.key === "ArrowUp") next = prev - 1;
            else if (e?.key === "ArrowDown") next = prev + 1;
            else if (e?.key === "PageUp") next = prev - Math.max(1, reasonH);
            else if (e?.key === "PageDown") next = prev + Math.max(1, reasonH);
            else if (e?.key === "Home") next = 0;
            else if (e?.key === "End") next = maxOffset;
            else return;

            e.preventDefault?.();
            e.stopPropagation?.();
            reasonScrollOffset.value = clamp(next, 0, maxOffset);
          },
        },
        () =>
          h(
            TBox as any,
            {
              x: 0,
              y: 0,
              w: boxW,
              h: boxH,
              border: true,
              padding: 1,
              title: t("approvalDialog.title"),
              style: boxStyle,
            },
            () => nodes,
          ),
      );
    };
  },
});
