import { useMemo, useState } from "react";
import {
  exportListRows,
  parseListTemplate,
  renderListRows,
  type ListRow,
} from "../regex/list/list-template";
import type { RegexMatchResult } from "../regex/model/match";
import {
  DEFAULT_REGEX_LIMITS,
  TEMPLATE_PRESENTATION_LIMITS,
} from "../regex/execution/request-limits";

function download(text: string, format: string): void {
  const mediaTypes: Record<string, string> = {
    text: "text/plain;charset=utf-8",
    csv: "text/csv;charset=utf-8",
    json: "application/json",
    ndjson: "application/x-ndjson",
  };
  const extensions: Record<string, string> = {
    text: "txt",
    csv: "csv",
    json: "json",
    ndjson: "ndjson",
  };
  const url = URL.createObjectURL(
    new Blob([text], { type: mediaTypes[format] }),
  );
  const anchor = document.createElement("a");
  anchor.href = url;
  anchor.download = `regex-extraction.${extensions[format]}`;
  anchor.click();
  window.setTimeout(() => URL.revokeObjectURL(url), 1_000);
}

function ListPreview({ rows }: { readonly rows: readonly ListRow[] }) {
  return rows.length > 0 ? (
    <ol className="list-preview">
      {rows
        .slice(0, TEMPLATE_PRESENTATION_LIMITS.listPreviewRows)
        .map((row) => (
          <li key={row.matchNumber}>
            <code>{row.value || "∅"}</code>
          </li>
        ))}
    </ol>
  ) : (
    <p className="empty-state">No rows generated.</p>
  );
}

export function ListPanel({
  templateSource,
  onTemplateChange,
  matches,
  subject,
  sourceResultTruncated = false,
}: {
  readonly templateSource: string;
  readonly onTemplateChange: (value: string) => void;
  readonly matches: readonly RegexMatchResult[];
  readonly subject: string;
  readonly sourceResultTruncated?: boolean;
}) {
  const [format, setFormat] = useState<"text" | "csv" | "json" | "ndjson">(
    "text",
  );
  const template = useMemo(
    () => parseListTemplate(templateSource),
    [templateSource],
  );
  const rendered = useMemo(
    () =>
      renderListRows(
        template,
        matches,
        subject,
        "test-text",
        sourceResultTruncated,
      ),
    [matches, sourceResultTruncated, subject, template],
  );
  const rows = rendered.rows;
  const incompleteRows = rows.filter((row) => !row.complete);
  const renderedTokenChips = template.tokens.slice(
    0,
    TEMPLATE_PRESENTATION_LIMITS.listTokenChips,
  );
  return (
    <div className="mode-grid">
      <section className="panel editor-panel">
        <header className="panel-heading">
          <div>
            <p className="eyebrow">Typed, non-executable template</p>
            <h2>List / extraction template</h2>
          </div>
        </header>
        <label className="stacked-control">
          <span>Template</span>
          <textarea
            value={templateSource}
            onChange={(event) => onTemplateChange(event.target.value)}
            rows={4}
            maxLength={DEFAULT_REGEX_LIMITS.maximumListTemplateUtf16}
            aria-invalid={!rendered.templateAccepted}
          />
        </label>
        <p className="field-help">
          Tokens: <code>$0</code>, <code>$1…$1000</code>,{" "}
          <code>${"{name}"}</code>, <code>{"{{match}}"}</code>,{" "}
          <code>{"{{line}}"}</code>, and <code>{"{{source}}"}</code>.
        </p>
        <p className="field-help">
          {templateSource.length.toLocaleString()} /{" "}
          {DEFAULT_REGEX_LIMITS.maximumListTemplateUtf16.toLocaleString()}{" "}
          UTF-16 units
        </p>
        {!rendered.templateAccepted ? (
          <p
            className="render-limit-banner"
            role="alert"
            data-testid="list-template-input-limit"
          >
            <strong>List template rejected.</strong> {rendered.warnings[0]}
            Exact rendering and export are disabled.
          </p>
        ) : null}
        <div className="token-chips" aria-label="Parsed list template">
          {renderedTokenChips.map((token, index) => (
            <span key={`${token.kind}-${index}`}>{token.kind}</span>
          ))}
        </div>
        {template.tokens.length >
        TEMPLATE_PRESENTATION_LIMITS.listTokenChips ? (
          <p
            className="render-limit-banner"
            data-testid="list-token-render-limit"
          >
            <strong>Token chips limited.</strong> Showing the first{" "}
            {TEMPLATE_PRESENTATION_LIMITS.listTokenChips.toLocaleString()} of{" "}
            {template.tokens.length.toLocaleString()} complete template tokens.
          </p>
        ) : null}
      </section>
      <section className="panel output-panel">
        <header className="panel-heading">
          <div>
            <p className="eyebrow">Bounded preview</p>
            <h2>Generated rows</h2>
          </div>
          <span>
            {rendered.templateAccepted
              ? rendered.sourceResultTruncated
                ? `${rows.length.toLocaleString()} returned · engine limit reached`
                : `${rows.length}${
                    rendered.truncated ? ` of ${matches.length}` : ""
                  }`
              : "Template unavailable"}
          </span>
        </header>
        <ListPreview rows={rows} />
        {rows.length > TEMPLATE_PRESENTATION_LIMITS.listPreviewRows ? (
          <p
            className="render-limit-banner"
            data-testid="list-row-render-limit"
          >
            <strong>Row preview limited.</strong> Showing the first{" "}
            {TEMPLATE_PRESENTATION_LIMITS.listPreviewRows.toLocaleString()} of{" "}
            {rows.length.toLocaleString()} generated rows. Eligible exports
            still contain every generated row.
          </p>
        ) : null}
        {incompleteRows.length > 0 ||
        rendered.truncated ||
        !rendered.templateAccepted ? (
          <p className="privacy-warning" role="alert">
            {!rendered.templateAccepted || rendered.truncated
              ? rendered.warnings[0]
              : `Exact values are unavailable for ${incompleteRows.length} row${
                  incompleteRows.length === 1 ? "" : "s"
                }.`}{" "}
            Export is disabled so bounded previews cannot be mistaken for
            complete data.
          </p>
        ) : null}
      </section>
      <section className="panel export-panel mode-span">
        <header className="panel-heading compact">
          <h2>Export extraction</h2>
        </header>
        <div className="inline-controls">
          <label>
            <span>Format</span>
            <select
              value={format}
              onChange={(event) =>
                setFormat(
                  event.target.value as "text" | "csv" | "json" | "ndjson",
                )
              }
            >
              <option value="text">Plain text</option>
              <option value="csv">CSV</option>
              <option value="json">JSON</option>
              <option value="ndjson">NDJSON</option>
            </select>
          </label>
          <button
            type="button"
            className="primary-button"
            disabled={
              rows.length === 0 ||
              incompleteRows.length > 0 ||
              rendered.truncated ||
              !rendered.templateAccepted
            }
            onClick={() => download(exportListRows(rows, format), format)}
          >
            Download {format.toUpperCase()}
          </button>
        </div>
      </section>
    </div>
  );
}
