"use client";

import { AlignCenter, AlignJustify, AlignLeft, AlignRight, Bold, Code, Asterisk, Eraser, Image as ImageIcon, Italic, KeyRound, Link2, List, ListOrdered, Minus, Pencil, Strikethrough, Table, Underline, Unlink, X } from "lucide-react";
import { MERGE_TAGS } from "@/lib/blocks/merge";
import { stripMarkup } from "@/lib/blocks/markup";
import { cn } from "@/lib/utils";

/** A text edit: new text plus the selection to restore. Pure functions, so they are easy to test. */
export interface TextEdit { text: string; start: number; end: number }
export type TextOp = (text: string, start: number, end: number) => TextEdit;

const ALIGN_RE = /^::(left|center|right|justify) /;
const LINE_MARK = /^(#{2,3} |- |\d+\. )/;

const lineBounds = (t: string, s: number, e: number) => {
  const from = t.lastIndexOf("\n", s - 1) + 1;
  const toIdx = t.indexOf("\n", e);
  return { from, to: toIdx === -1 ? t.length : toIdx };
};
/** Start of the paragraph that contains `pos` (alignment lives on a paragraph's first line). */
const paragraphStart = (t: string, pos: number) => {
  const idx = t.lastIndexOf("\n\n", Math.max(pos - 1, 0));
  return idx === -1 ? 0 : idx + 2;
};

export const wrap = (pre: string, post: string, placeholder: string): TextOp => (t, s, e) => {
  const sel = t.slice(s, e) || placeholder;
  return { text: t.slice(0, s) + pre + sel + post + t.slice(e), start: s + pre.length, end: s + pre.length + sel.length };
};

export const linePrefix = (prefix: "- " | "1. " | "## " | "### " | ""): TextOp => (t, s, e) => {
  const { from, to } = lineBounds(t, s, e);
  const lines = t.slice(from, to).split("\n");
  const all = lines.every((l) => {
    const rest = l.replace(ALIGN_RE, "");
    return prefix === "1. " ? /^\d+\. /.test(rest) : prefix !== "" && rest.startsWith(prefix);
  });
  const out = lines.map((l, i) => {
    const am = ALIGN_RE.exec(l);
    const lead = am ? am[0] : "";
    const rest = l.slice(lead.length).replace(LINE_MARK, "");
    if (all || prefix === "") return lead + rest;
    return lead + (prefix === "1. " ? `${i + 1}. ` : prefix) + rest;
  });
  const block = out.join("\n");
  return { text: t.slice(0, from) + block + t.slice(to), start: from, end: from + block.length };
};

export const link: TextOp = (t, s, e) => {
  const text = t.slice(s, e) || "link text";
  const urlStart = s + text.length + 3;
  return { text: t.slice(0, s) + `[${text}](https://)` + t.slice(e), start: urlStart, end: urlStart + 8 };
};

/** Removes the link around the cursor or selection, keeping its text. */
export const unlink: TextOp = (t, s, e) => {
  const re = /\[([^\]\n]+)\]\(([^)\s]+)\)/g;
  let m: RegExpExecArray | null;
  while ((m = re.exec(t))) {
    const a = m.index;
    const b = a + m[0].length;
    if (s <= b && e >= a) return { text: t.slice(0, a) + m[1] + t.slice(b), start: a, end: a + m[1]!.length };
  }
  return { text: t, start: s, end: e };
};

/** Clears formatting from the selection (or the current line when nothing is selected). */
export const clearFormat: TextOp = (t, s, e) => {
  const { from, to } = s === e ? lineBounds(t, s, e) : { from: s, to: e };
  const cleaned = stripMarkup(t.slice(from, to));
  return { text: t.slice(0, from) + cleaned + t.slice(to), start: from, end: from + cleaned.length };
};

export const align = (a: "left" | "center" | "right" | "justify"): TextOp => (t, s, e) => {
  const from = paragraphStart(t, s);
  const rest = t.slice(from).replace(ALIGN_RE, "");
  const mark = a === "left" ? "" : `::${a} `;
  const removed = t.length - from - rest.length;
  return { text: t.slice(0, from) + mark + rest, start: s + mark.length - removed, end: e + mark.length - removed };
};

const block = (insert: string, selectFrom: number, selectLen: number): TextOp => (t, s, e) => {
  const before = t.slice(0, s);
  const lead = before === "" || before.endsWith("\n\n") ? "" : before.endsWith("\n") ? "\n" : "\n\n";
  const after = t.slice(e);
  const trail = after === "" || after.startsWith("\n\n") ? "" : after.startsWith("\n") ? "\n" : "\n\n";
  const start = s + lead.length + selectFrom;
  return { text: before + lead + insert + trail + after, start, end: start + selectLen };
};

export const hr: TextOp = block("---", 0, 0);
export const image: TextOp = block("![description](https://)", 14, 8);
export const table: TextOp = block("| Heading | Heading |\n| --- | --- |\n| Cell | Cell |\n| Cell | Cell |", 2, 7);

export const button = (group = false): TextOp => (t, s, e) => {
  const label = t.slice(s, e) || "Button";
  const text = group ? `[[${label}|/link]] [[Button 2|/link]]` : `[[${label}|/link]]`;
  return { text: t.slice(0, s) + text + t.slice(e), start: s + 2, end: s + 2 + label.length };
};

export const shortcut = (tag: string): TextOp => (t, s, e) => {
  const ins = `{{${tag}}}`;
  return { text: t.slice(0, s) + ins + t.slice(e), start: s + ins.length, end: s + ins.length };
};

/** Paragraph / Heading / Sub-heading from the "Styles" menu. */
export const style = (v: "p" | "h2" | "h3"): TextOp => linePrefix(v === "p" ? "" : v === "h2" ? "## " : "### ");

const btn = "inline-flex size-9 items-center justify-center rounded-md border border-white/25 text-white hover:bg-white/15 focus-visible:outline-2 focus-visible:outline-white disabled:opacity-40";
const wide = "inline-flex h-9 items-center gap-1 rounded-md border border-white/25 px-2.5 text-xs font-bold text-white hover:bg-white/15";

function ToolBtn({ apply, label, op, children }: { apply: (op: TextOp) => void; label: string; op: TextOp; children: React.ReactNode }) {
  return (
    <button type="button" className={btn} aria-label={label} title={label} onMouseDown={(e) => e.preventDefault()} onClick={() => apply(op)}>{children}</button>
  );
}

interface Props {
  apply: (op: TextOp) => void;
  onWidget: () => void;
  previewOn: boolean;
  onTogglePreview: () => void;
  /** Anchor to the right edge of the article instead of the left (for the last column). */
  alignRight?: boolean;
  onClose: () => void;
  /** "title" is the short version for single-line Section/Article titles: inline styles, links and shortcuts only. */
  mode?: "full" | "title";
}

/** The dark two-row formatting bar that appears above an article when you click inside it. It floats, so it never moves the content under the pointer. */
export function TextToolbar({ apply, onWidget, previewOn, onTogglePreview, alignRight, onClose, mode = "full" }: Props) {
  const title = mode === "title";
  const keep = (e: React.MouseEvent) => e.preventDefault(); // keep the text caret where it is
  const sep = <span aria-hidden className="mx-1 h-6 w-px bg-white/25" />;
  if (title) {
    // ShowOff's title bar: one compact row (styles, alignment, links, shortcuts). Esc hides it.
    return (
      <div role="toolbar" aria-label="Title formatting" className={cn("absolute bottom-full z-30 mb-1 flex w-max max-w-[calc(100vw-3rem)] flex-wrap items-center gap-1.5 rounded-xl bg-[var(--ed-dark)] p-2 shadow-xl", alignRight ? "right-0" : "left-0")}>
        <ToolBtn apply={apply} label="Bold" op={wrap("**", "**", "bold text")}><Bold className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Italic" op={wrap("*", "*", "italic text")}><Italic className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Underline" op={wrap("__", "__", "underlined text")}><Underline className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Strikethrough" op={wrap("~~", "~~", "struck text")}><Strikethrough className="size-4" aria-hidden /></ToolBtn>
        {sep}
        <ToolBtn apply={apply} label="Align left" op={align("left")}><AlignLeft className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Align center" op={align("center")}><AlignCenter className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Align right" op={align("right")}><AlignRight className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Justify" op={align("justify")}><AlignJustify className="size-4" aria-hidden /></ToolBtn>
        {sep}
        <ToolBtn apply={apply} label="Insert link" op={link}><Link2 className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Remove link" op={unlink}><Unlink className="size-4" aria-hidden /></ToolBtn>
        {sep}
        <span className="relative inline-flex size-9 items-center justify-center rounded-md border border-white/25 hover:bg-white/15">
          <Asterisk className="pointer-events-none absolute size-4 text-white" aria-hidden />
          <label className="sr-only" htmlFor="tb-shortcut">Insert shortcut</label>
          <select id="tb-shortcut" title="Insert shortcut" defaultValue="" onChange={(e) => { if (e.target.value) apply(shortcut(e.target.value)); e.target.value = ""; }} className="absolute inset-0 size-full cursor-pointer appearance-none rounded-md bg-transparent text-transparent opacity-0 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-white">
            <option value="" className="text-black">Insert…</option>
            {Object.keys(MERGE_TAGS).map((k) => <option key={k} value={k} className="text-black">{`{{${k}}}`}</option>)}
          </select>
        </span>
      </div>
    );
  }
  return (
    <div role="toolbar" aria-label="Text formatting" className={cn("absolute bottom-full z-30 mb-2 w-[min(46rem,calc(100vw-3rem))] space-y-2 rounded-2xl bg-[var(--ed-dark)] p-3 shadow-xl", alignRight ? "right-0" : "left-0")}>
      <div className="flex flex-wrap items-center gap-1.5">
        <ToolBtn apply={apply} label="Bold" op={wrap("**", "**", "bold text")}><Bold className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Italic" op={wrap("*", "*", "italic text")}><Italic className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Underline" op={wrap("__", "__", "underlined text")}><Underline className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Strikethrough" op={wrap("~~", "~~", "struck text")}><Strikethrough className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Clear formatting" op={clearFormat}><Eraser className="size-4" aria-hidden /></ToolBtn>
        {sep}
        <ToolBtn apply={apply} label="Numbered list" op={linePrefix("1. ")}><ListOrdered className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Bulleted list" op={linePrefix("- ")}><List className="size-4" aria-hidden /></ToolBtn>
        {sep}
        <ToolBtn apply={apply} label="Align left" op={align("left")}><AlignLeft className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Align center" op={align("center")}><AlignCenter className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Align right" op={align("right")}><AlignRight className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Justify" op={align("justify")}><AlignJustify className="size-4" aria-hidden /></ToolBtn>
        {sep}
        <ToolBtn apply={apply} label="Insert link" op={link}><Link2 className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Remove link" op={unlink}><Unlink className="size-4" aria-hidden /></ToolBtn>
        <button type="button" className={cn(btn, previewOn && "bg-white/25")} aria-label="Show live preview" aria-pressed={previewOn} title="Show live preview" onMouseDown={keep} onClick={onTogglePreview}><Code className="size-4" aria-hidden /></button>
        <button type="button" className={cn(btn, "ml-auto")} aria-label="Hide formatting bar" title="Hide formatting bar (Esc)" onMouseDown={keep} onClick={onClose}><X className="size-4" aria-hidden /></button>
      </div>
      <div className="flex flex-wrap items-center gap-1.5">
        <ToolBtn apply={apply} label="Horizontal rule" op={hr}><Minus className="size-4" aria-hidden /></ToolBtn>
        <ToolBtn apply={apply} label="Insert table" op={table}><Table className="size-4" aria-hidden /></ToolBtn>
        {sep}
        <ToolBtn apply={apply} label="Insert image from a web address" op={image}><ImageIcon className="size-4" aria-hidden /></ToolBtn>
        <button type="button" className={wide} onMouseDown={keep} onClick={onWidget}><Pencil className="size-3.5" aria-hidden />Widget</button>
        <button type="button" className={wide} onMouseDown={keep} onClick={() => apply(button())}>Button</button>
        <button type="button" className={wide} onMouseDown={keep} onClick={() => apply(button(true))}>Button Group</button>
        {sep}
        <span className="relative inline-flex items-center">
          <KeyRound className="pointer-events-none absolute left-2.5 size-4 text-white" aria-hidden />
          <label className="sr-only" htmlFor="tb-shortcut">Insert shortcut</label>
          <select id="tb-shortcut" defaultValue="" onChange={(e) => { if (e.target.value) apply(shortcut(e.target.value)); e.target.value = ""; }} className="h-9 w-24 appearance-none rounded-md border border-white/25 bg-transparent pl-8 pr-2 text-xs font-bold text-white">
            <option value="" className="text-black">Insert</option>
            {Object.keys(MERGE_TAGS).map((k) => <option key={k} value={k} className="text-black">{`{{${k}}}`}</option>)}
          </select>
        </span>
        <label className="sr-only" htmlFor="tb-styles">Text style</label>
        <select id="tb-styles" defaultValue="" onChange={(e) => { if (e.target.value) apply(style(e.target.value as "p" | "h2" | "h3")); e.target.value = ""; }} className="h-9 min-w-36 rounded-md border border-white/25 bg-transparent px-2 text-xs font-bold text-white">
          <option value="" className="text-black">Styles</option>
          <option value="p" className="text-black">Paragraph</option>
          <option value="h2" className="text-black">Heading</option>
          <option value="h3" className="text-black">Sub-heading</option>
        </select>
      </div>
    </div>
  );
}
