import type {
  ReplacementContributionPreview,
  ReplacementPreview,
} from "../regex/replacement/replacement-preview";

function rangeLabel(
  range:
    | {
        readonly startUtf16: number;
        readonly endUtf16: number;
      }
    | undefined,
  unavailableLabel = "unavailable",
): string {
  return range
    ? `${range.startUtf16.toLocaleString()}…${range.endUtf16.toLocaleString()}`
    : unavailableLabel;
}

function previewLabel(
  value: string,
  lengthUtf16: number,
  truncated: boolean,
): string {
  if (value === "" && lengthUtf16 === 0) return "∅ empty";
  return `${value}${truncated ? " …" : ""}`;
}

export function ReplacementPreviewPanel({
  preview,
  selectedContributionId,
  onSelectContribution,
}: {
  readonly preview?: ReplacementPreview;
  readonly selectedContributionId?: string;
  readonly onSelectContribution: (
    contribution: ReplacementContributionPreview,
  ) => void;
}) {
  return (
    <section
      className="panel replacement-preview-panel mode-span"
      aria-labelledby="replacement-preview-heading"
    >
      <header className="panel-heading">
        <div>
          <p className="eyebrow">Actual matches · derived range mapping</p>
          <h2 id="replacement-preview-heading">Per-match preview</h2>
        </div>
        <span>
          {preview
            ? `${preview.matches.length.toLocaleString()} of ${preview.totalMatches.toLocaleString()} matches`
            : "Waiting for replacement output"}
        </span>
      </header>
      {preview?.truncated ? (
        <p
          className="render-limit-banner"
          role="status"
          data-testid="replacement-preview-render-limit"
        >
          <strong>Replacement preview limited.</strong> Showing{" "}
          {preview.matches.length.toLocaleString()} of{" "}
          {preview.totalMatches.toLocaleString()} matches and{" "}
          {preview.renderedContributions.toLocaleString()} of{" "}
          {preview.totalContributions.toLocaleString()} token contributions. The
          complete bounded output remains in the output editor.
        </p>
      ) : null}
      {(preview?.matches.length ?? 0) > 0 ? (
        <ol className="replacement-preview-list">
          {preview?.matches.map((match) => (
            <li key={match.id}>
              <article className="replacement-match-preview">
                <header>
                  <strong>Match {match.matchNumber.toLocaleString()}</strong>
                  <small>
                    source {rangeLabel(match.originalRange)} → output{" "}
                    {rangeLabel(
                      match.outputRange,
                      "not available in bounded output",
                    )}
                  </small>
                </header>
                <div className="replacement-comparison">
                  <div>
                    <span>Original match</span>
                    <code>
                      {previewLabel(
                        match.originalPreview,
                        match.originalLengthUtf16,
                        match.originalTruncated,
                      )}
                    </code>
                  </div>
                  <div>
                    <span>Replacement result</span>
                    <code>
                      {previewLabel(
                        match.replacementPreview,
                        match.replacementLengthUtf16,
                        match.replacementTruncated,
                      )}
                    </code>
                  </div>
                </div>
                {match.contributions.length > 0 ? (
                  <ul
                    className="replacement-contribution-list"
                    aria-label={`Match ${match.matchNumber} replacement token contributions`}
                  >
                    {match.contributions.map((contribution) => (
                      <li key={contribution.id}>
                        <button
                          type="button"
                          className={
                            contribution.id === selectedContributionId
                              ? "is-selected"
                              : undefined
                          }
                          aria-pressed={
                            contribution.id === selectedContributionId
                          }
                          onClick={() => onSelectContribution(contribution)}
                        >
                          <code>{contribution.token.raw || "∅"}</code>
                          <span>
                            {previewLabel(
                              contribution.valuePreview,
                              contribution.valueLengthUtf16,
                              contribution.valueTruncated,
                            )}
                          </span>
                          <small>
                            {contribution.token.explanation} · source{" "}
                            {rangeLabel(
                              contribution.sourceRange,
                              "literal or non-participating",
                            )}{" "}
                            · output{" "}
                            {rangeLabel(
                              contribution.outputRange,
                              "not available in bounded output",
                            )}
                          </small>
                        </button>
                      </li>
                    ))}
                  </ul>
                ) : (
                  <p className="empty-state">
                    This empty template removes the match.
                  </p>
                )}
              </article>
            </li>
          ))}
        </ol>
      ) : (
        <p className="empty-state">
          Run a valid pattern in Replace mode to inspect each substitution.
        </p>
      )}
    </section>
  );
}
