import type { ReadableSegment, SegmentLanguage } from "./types";

export type ParagraphKind = "readable" | "formula" | "table" | "empty";

export function normalizeReadableText(value: string): string {
  return value
    .replace(/\r\n?/g, "\n")
    .split("\n")
    .map((line) => line.replace(/[\t ]+/g, " ").trim())
    .join("\n")
    .replace(/\n{3,}/g, "\n\n")
    .trim();
}

export function detectPrimaryLanguage(value: string): SegmentLanguage {
  const hanCount = value.match(/\p{Script=Han}/gu)?.length ?? 0;
  const latinCount = value.match(/\p{Script=Latin}/gu)?.length ?? 0;
  return hanCount * 2 >= latinCount ? "zh-CN" : "en-US";
}

export function classifyParagraph(value: string): ParagraphKind {
  const text = value.trim();
  if (!text) return "empty";

  const tabColumns = text.split(/\t+/).filter(Boolean).length;
  const spacedColumns = text.split(/\s{2,}/).filter(Boolean);
  const numericColumns = spacedColumns.filter((column) => /\d/.test(column));
  if (
    tabColumns >= 3 ||
    (spacedColumns.length >= 3 && numericColumns.length >= 2)
  ) {
    return "table";
  }

  const formulaSymbols = text.match(/[=+×÷∑∫√≠≤≥±∞^_₀-₉⁰-⁹α-ωΑ-Ω]/gu)?.length ?? 0;
  const proseCharacters = text.match(/[\p{Script=Han}\p{Script=Latin}]/gu)?.length ?? 0;
  if (formulaSymbols >= 3 && formulaSymbols / Math.max(proseCharacters, 1) > 0.16) {
    return "formula";
  }

  return "readable";
}

function splitLongChunk(chunk: string, maxCharacters: number): string[] {
  if (chunk.length <= maxCharacters) return [chunk];

  const pieces: string[] = [];
  let remaining = chunk;
  while (remaining.length > maxCharacters) {
    const window = remaining.slice(0, maxCharacters + 1);
    const candidates = [
      window.lastIndexOf(" "),
      window.lastIndexOf("，"),
      window.lastIndexOf(","),
      window.lastIndexOf("；"),
      window.lastIndexOf(";"),
    ];
    const breakAt = Math.max(...candidates);
    const cut = breakAt >= Math.floor(maxCharacters * 0.45) ? breakAt + 1 : maxCharacters;
    pieces.push(remaining.slice(0, cut).trim());
    remaining = remaining.slice(cut).trim();
  }
  if (remaining) pieces.push(remaining);
  return pieces;
}

export function splitReadableSegments(
  value: string,
  maxCharacters = 900,
): ReadableSegment[] {
  if (!Number.isInteger(maxCharacters) || maxCharacters < 20) {
    throw new RangeError("maxCharacters must be an integer of at least 20");
  }

  const normalized = normalizeReadableText(value);
  if (!normalized) return [];

  const naturalChunks = normalized.match(/[^。！？!?…\n]+(?:[。！？!?…]+|(?=\n|$))/gu) ?? [];
  return naturalChunks
    .flatMap((chunk) => splitLongChunk(chunk.trim(), maxCharacters))
    .filter(Boolean)
    .map((text) => ({ text, language: detectPrimaryLanguage(text) }));
}

export async function contentFingerprint(value: string): Promise<string> {
  const normalized = normalizeReadableText(value);
  const digest = await crypto.subtle.digest(
    "SHA-256",
    new TextEncoder().encode(normalized),
  );
  return Array.from(new Uint8Array(digest), (byte) =>
    byte.toString(16).padStart(2, "0"),
  ).join("");
}
