export interface PrototypeContext {
  text: string;
  noteCount: number;
  truncated: boolean;
}

export interface PrototypeContextRequirement {
  id: string;
  code: string;
  title: string;
  description?: string;
}

export interface PrototypeContextOptions {
  requirements?: PrototypeContextRequirement[];
}

const QY_NOTE_PATTERN = /QY\d{3,}/gi;
const MAX_NOTE_SNIPPETS = 40;
const MAX_REQUIREMENT_SNIPPETS = 30;
type PrototypeAnchor = { index: number; kind: 'note' | 'requirement' };

export function buildPrototypeContext(
  content: string,
  maxChars = 12000,
  options: PrototypeContextOptions = {},
): PrototypeContext {
  const normalized = content.replace(/\s+/g, ' ').trim();
  const matches = Array.from(normalized.matchAll(QY_NOTE_PATTERN));
  const requirementAnchors = findRequirementAnchors(normalized, options.requirements ?? []);

  if (matches.length === 0 && requirementAnchors.length === 0) {
    return {
      text: normalized.slice(0, maxChars),
      noteCount: 0,
      truncated: normalized.length > maxChars,
    };
  }

  const anchors: PrototypeAnchor[] = [
    ...matches.slice(0, MAX_NOTE_SNIPPETS).map((match) => ({ index: match.index ?? 0, kind: 'note' as const })),
    ...requirementAnchors.slice(0, MAX_REQUIREMENT_SNIPPETS).map((index) => ({ index, kind: 'requirement' as const })),
  ].sort((a, b) => a.index - b.index);

  const windowSize = Math.max(280, Math.floor(maxChars / Math.min(anchors.length, 12)));
  const ranges = anchors
    .map((anchor) => {
      return {
        start: Math.max(0, anchor.index - 120),
        end: getAnchorRangeEnd(normalized, anchor, windowSize),
      };
    })
    .sort((a, b) => a.start - b.start);

  const mergedRanges: Array<{ start: number; end: number }> = [];
  for (const range of ranges) {
    const last = mergedRanges[mergedRanges.length - 1];
    if (!last || range.start > last.end) {
      mergedRanges.push({ ...range });
    } else {
      last.end = Math.max(last.end, range.end);
    }
  }

  const snippets: string[] = [];
  let usedChars = 0;
  let truncated = matches.length > MAX_NOTE_SNIPPETS;
  for (const range of mergedRanges) {
    const separatorLength = snippets.length > 0 ? '\n\n---\n\n'.length : 0;
    const remaining = maxChars - usedChars - separatorLength;
    if (remaining <= 0) {
      truncated = true;
      break;
    }

    const snippet = normalized.slice(range.start, range.end).trim();
    if (snippet.length > remaining) {
      snippets.push(snippet.slice(0, remaining).trim());
      usedChars = maxChars;
      truncated = true;
      break;
    }

    snippets.push(snippet);
    usedChars += separatorLength + snippet.length;
  }

  const text = snippets.join('\n\n---\n\n');
  return {
    text,
    noteCount: matches.length,
    truncated,
  };
}

function findRequirementAnchors(content: string, requirements: PrototypeContextRequirement[]): number[] {
  const lowerContent = content.toLowerCase();
  const anchors = new Set<number>();

  for (const requirement of requirements) {
    for (const term of buildRequirementMatchTerms(requirement)) {
      const lowerTerm = term.toLowerCase();
      let cursor = 0;
      while (cursor < lowerContent.length) {
        const index = lowerContent.indexOf(lowerTerm, cursor);
        if (index < 0) break;
        anchors.add(index);
        cursor = index + Math.max(lowerTerm.length, 1);
      }
    }
  }

  return [...anchors].sort((a, b) => a - b);
}

function buildRequirementMatchTerms(requirement: PrototypeContextRequirement): string[] {
  const terms = new Set<string>();
  addMatchTerm(terms, requirement.id);
  addMatchTerm(terms, requirement.code);
  addMatchTerm(terms, requirement.title);

  for (const part of (requirement.description ?? '').split(/[，,。.;；、\n\r\t]+/)) {
    addMatchTerm(terms, part);
  }

  return [...terms];
}

function addMatchTerm(terms: Set<string>, value?: string): void {
  const term = (value ?? '').replace(/\s+/g, ' ').trim();
  if (term.length < 2) return;
  terms.add(term);
}

function getAnchorRangeEnd(content: string, anchor: PrototypeAnchor, windowSize: number): number {
  const fallbackEnd = Math.min(content.length, anchor.index + windowSize);
  if (anchor.kind === 'note') return fallbackEnd;

  const sentenceEnd = findSentenceEnd(content, anchor.index, Math.min(windowSize, 320));
  return sentenceEnd ?? Math.min(content.length, anchor.index + Math.min(windowSize, 220));
}

function findSentenceEnd(content: string, start: number, maxDistance: number): number | null {
  const limit = Math.min(content.length, start + maxDistance);
  for (let i = start; i < limit; i++) {
    if ('。；;!?！？'.includes(content[i])) {
      return i + 1;
    }
  }
  return null;
}
