"use client";

import { Fragment, useMemo, useState } from "react";
import type {
  ScriptDescriptionPart,
  ScriptDimension,
  ScriptElementRow,
  ScriptFacet,
  ScriptParagraphRow,
} from "@/lib/types";

const FACETS = [
  ["theme", "主题", "theme", "主题描述"],
  ["form", "形式", "form", "形式描述"],
  ["function", "作用", "action", "作用描述"],
  ["feeling", "感受", "feeling", "感受描述"],
] as const;

const PARA_COL_WIDTH = 100;
const ATOM_TYPE_COL_WIDTH = 72;
const ATOM_DIM_COL_WIDTH = 108;
const ATOM_VAL_COL_WIDTH = 108;
const OUTLINE_DESC_WIDTH = 155;
const FIXED_COLS_WIDTH = 150 + (OUTLINE_DESC_WIDTH + ATOM_TYPE_COL_WIDTH + ATOM_DIM_COL_WIDTH + ATOM_VAL_COL_WIDTH) * 4 + 400;

interface Props {
  paragraphs: ScriptParagraphRow[];
  elements: ScriptElementRow[];
  onOpenElement: (id: number | string) => void;
}

export function ScriptTable({ paragraphs, elements, onOpenElement }: Props) {
  const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
  const maxDepth = Math.max(1, ...paragraphs.map((row) => Number(row.depth || 0) + 1));
  const elementLookup = useMemo(() => buildElementLookup(elements), [elements]);
  const hiddenIds = useMemo(() => descendantIds(paragraphs, collapsed), [paragraphs, collapsed]);
  const rowsWithChildren = useMemo(() => {
    const parents = new Set(paragraphs.map((row) => row.parentId).filter((value) => value != null).map(String));
    return new Set(paragraphs.filter((row) => parents.has(String(row.id)) || parents.has(String(row.rowId))).map((row) => String(row.id)));
  }, [paragraphs]);

  const toggle = (id: number | string) => setCollapsed((current) => {
    const next = new Set(current);
    const key = String(id);
    if (next.has(key)) next.delete(key);
    else next.add(key);
    return next;
  });

  return <section className="originalScriptTable" aria-label="完整脚本创作表">
    <div className="scriptTableTitle">
      <strong>创作脚本</strong>
      <span className="script-legend"><span className="legend-item"><i className="legend-swatch" />脚本元素</span></span>
    </div>
    <div className="script-table-wrapper" tabIndex={0} aria-label="可横向和纵向滚动的脚本创作表">
      <table className="script-table" style={{ width: maxDepth * PARA_COL_WIDTH + FIXED_COLS_WIDTH }}>
        <caption className="srOnly">完整脚本创作表</caption>
        <colgroup>
          {Array.from({ length: maxDepth }, (_, index) => <col key={`paragraph-${index}`} style={{ width: PARA_COL_WIDTH }} />)}
          <col style={{ width: 150 }} />
          {FACETS.flatMap(([key]) => [
            <col key={`${key}-description`} style={{ width: OUTLINE_DESC_WIDTH }} />,
            <col key={`${key}-type`} style={{ width: ATOM_TYPE_COL_WIDTH }} />,
            <col key={`${key}-name`} style={{ width: ATOM_DIM_COL_WIDTH }} />,
            <col key={`${key}-value`} style={{ width: ATOM_VAL_COL_WIDTH }} />,
          ])}
          <col style={{ width: 400 }} />
        </colgroup>
        <TableHeader maxDepth={maxDepth} />
        <tbody>{paragraphs.map((paragraph) => hiddenIds.has(String(paragraph.id)) ? null : <tr key={String(paragraph.rowId)} className={`script-row-l${Number(paragraph.depth || 0) + 1}`}>
          {Array.from({ length: maxDepth }, (_, columnIndex) => {
            const isOwnLevel = columnIndex === Number(paragraph.depth || 0);
            const isLast = columnIndex === maxDepth - 1;
            return <td key={columnIndex} className={`col-paragraph${isLast ? " col-paragraph-last" : ""}`} style={{ left: columnIndex * PARA_COL_WIDTH }}>
              {isOwnLevel ? <>
                {rowsWithChildren.has(String(paragraph.id)) ? <button type="button" className={`collapse-toggle${collapsed.has(String(paragraph.id)) ? " collapsed" : ""}`} aria-label={`${collapsed.has(String(paragraph.id)) ? "展开" : "折叠"}${paragraph.name}`} aria-expanded={!collapsed.has(String(paragraph.id))} onClick={() => toggle(paragraph.id)}>{collapsed.has(String(paragraph.id)) ? "▶" : "▼"}</button> : null}
                {paragraph.name || "未命名"}
              </> : null}
            </td>;
          })}
          <td className="col-range"><div className="content-range-tags">{paragraph.contentRange.map((tag, index) => <span className="cr-tag" key={`${tag}-${index}`}>{tag}</span>)}</div></td>
          {FACETS.map(([key, , section]) => <FacetCells key={key} section={section} facet={paragraph.sections[key]} elementLookup={elementLookup} onOpenElement={onOpenElement} />)}
          <td className="col-full-desc"><ExpandableDescription parts={paragraph.fullDescriptionParts} fallback={paragraph.fullDescription} elementLookup={elementLookup} onOpenElement={onOpenElement} /></td>
        </tr>)}</tbody>
      </table>
    </div>
  </section>;
}

function TableHeader({ maxDepth }: { maxDepth: number }) {
  return <thead>
    <tr className="st-header-group">
      <th colSpan={maxDepth} className="col-paragraph" style={{ left: 0 }}>段落</th>
      <th rowSpan={3} className="col-range">段落范围</th>
      <th colSpan={16} className="col-outline">创作目录大纲（段本身）</th>
      <th rowSpan={3} className="col-full-desc">完整描述</th>
    </tr>
    <tr className="st-header-subgroup">
      {Array.from({ length: maxDepth }, (_, index) => <th key={index} rowSpan={2} className={`col-paragraph${index === maxDepth - 1 ? " col-paragraph-last" : ""}`} style={{ left: index * PARA_COL_WIDTH }}>L{index + 1}</th>)}
      {FACETS.map(([, label, section]) => <th key={section} colSpan={4} className={`col-outline col-outline-${section}`}>{label}</th>)}
    </tr>
    <tr className="st-header-col">
      {FACETS.map(([, , section, description]) => <Fragment key={section}>
        <th className={`col-outline col-outline-${section}`}>{description}</th>
        <th className={`col-outline col-outline-${section} col-atom-sub col-atom-type`}>维度类型</th>
        <th className={`col-outline col-outline-${section} col-atom-sub col-atom-dim`}>维度名</th>
        <th className={`col-outline col-outline-${section} col-atom-sub col-atom-val`}>维度值</th>
      </Fragment>)}
    </tr>
  </thead>;
}

function FacetCells({ section, facet, elementLookup, onOpenElement }: { section: string; facet: ScriptFacet; elementLookup: ElementLookup; onOpenElement: Props["onOpenElement"] }) {
  const dimensions = sortDimensions(facet.dimensions, section === "feeling");
  return <>
    <td className={`col-outline col-outline-${section}`}><ElementText parts={facet.descriptionParts} fallback={facet.description} elementLookup={elementLookup} onOpenElement={onOpenElement} /></td>
    {dimensions.length ? <td colSpan={3} className={`col-outline col-outline-${section} col-atom-group col-atom-group-${section}`}>
      <table className="atom-rows-table">
        <colgroup><col style={{ width: ATOM_TYPE_COL_WIDTH }} /><col style={{ width: ATOM_DIM_COL_WIDTH }} /><col style={{ width: ATOM_VAL_COL_WIDTH }} /></colgroup>
        <tbody>{dimensions.map((dimension, index) => {
          const dimClass = dimensionClass(dimension.type, section === "feeling");
          return <tr key={`${dimension.id ?? "dimension"}-${index}`}>
            <td className={`col-atom-type${dimClass ? ` ${dimClass}` : ""}`}>{section === "feeling" ? "—" : dimensionTypeLabel(dimension.type)}</td>
            <td className={`col-atom-dim${dimClass ? ` ${dimClass}` : ""}`}>{dimension.name || "—"}</td>
            <td className="col-atom-val"><span className={dimension.id != null ? "atom-val-with-id" : undefined} title={dimension.id != null ? `id: ${dimension.id}` : undefined}>{dimension.value || "—"}</span></td>
          </tr>;
        })}</tbody>
      </table>
    </td> : <td colSpan={3} className={`col-outline col-outline-${section} col-atom-group col-atom-group-${section}`} />}
  </>;
}

function ExpandableDescription({ parts, fallback, elementLookup, onOpenElement }: { parts: ScriptDescriptionPart[]; fallback?: string | null; elementLookup: ElementLookup; onOpenElement: Props["onOpenElement"] }) {
  const [expanded, setExpanded] = useState(false);
  return <div className={`cell-expandable${expanded ? " expanded" : ""}`} role="button" tabIndex={0} aria-expanded={expanded} onClick={() => setExpanded((value) => !value)} onKeyDown={(event) => {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      setExpanded((value) => !value);
    }
  }}><ElementText parts={parts} fallback={fallback} elementLookup={elementLookup} onOpenElement={onOpenElement} /></div>;
}

function ElementText({ parts, fallback, elementLookup, onOpenElement }: { parts: ScriptDescriptionPart[]; fallback?: string | null; elementLookup: ElementLookup; onOpenElement: Props["onOpenElement"] }) {
  if (!parts.length) return <>{highlightPlainText(fallback || "", elementLookup.names)}</>;
  return <>{parts.map((part, index) => part.type === "text"
    ? <Fragment key={index}>{highlightPlainText(part.text, elementLookup.names)}</Fragment>
    : part.resolved
      ? <button key={index} type="button" className="elem-hl-extended" title="在脚本元素中查看" onClick={(event) => { event.stopPropagation(); onOpenElement(part.id); }}>{part.name}</button>
      : <span key={index}>{part.text}</span>)}</>;
}

type ElementLookup = { names: string[] };

function buildElementLookup(elements: ScriptElementRow[]): ElementLookup {
  return { names: [...new Set(elements.map((element) => String(element.name || "").trim()).filter(Boolean))].sort((a, b) => b.length - a.length) };
}

function highlightPlainText(text: string, names: string[]) {
  if (!text || !names.length) return text || "";
  const pattern = names.map(escapeRegExp).join("|");
  if (!pattern) return text;
  const matcher = new RegExp(`(${pattern})`, "g");
  return text.split(matcher).map((part, index) => names.includes(part) ? <span key={index} className="elem-hl-extended">{part}</span> : <Fragment key={index}>{part}</Fragment>);
}

function escapeRegExp(value: string) {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function sortDimensions(dimensions: ScriptDimension[], isFeeling: boolean) {
  if (isFeeling) return [...dimensions];
  const rank: Record<string, number> = { "主维度": 0, "从维度": 1, "子元素": 2 };
  return [...dimensions].sort((left, right) => {
    const typeDiff = (rank[String(left.type || "")] ?? 99) - (rank[String(right.type || "")] ?? 99);
    if (typeDiff) return typeDiff;
    const nameDiff = String(left.name || "").localeCompare(String(right.name || ""), "zh-CN");
    return nameDiff || String(left.value || "").localeCompare(String(right.value || ""), "zh-CN");
  });
}

function dimensionTypeLabel(value?: string | null) {
  if (value === "主维度") return "主";
  if (value === "从维度") return "从";
  return value || "—";
}

function dimensionClass(value: string | null | undefined, isFeeling: boolean) {
  if (isFeeling) return "";
  if (value === "主维度") return "atom-dim-main";
  if (value === "从维度") return "atom-dim-sub";
  return "";
}

function descendantIds(paragraphs: ScriptParagraphRow[], collapsed: Set<string>) {
  const hidden = new Set<string>();
  let changed = true;
  while (changed) {
    changed = false;
    for (const row of paragraphs) {
      const parent = row.parentId == null ? null : String(row.parentId);
      if (!parent || (!collapsed.has(parent) && !hidden.has(parent))) continue;
      const key = String(row.id);
      if (!hidden.has(key)) {
        hidden.add(key);
        changed = true;
      }
    }
  }
  return hidden;
}
