import axios from "axios";
import FormData from "form-data";

export interface OcrWord {
  text: string;
  left: number;
  top: number;
  w: number;
  h: number;
  cx: number;
  cy: number;
}

// Runs OCR (Google Vision via PaymentOCRService, already running for payment-document
// extraction) on a screenshot ONCE and returns every detected word with its exact pixel bounding
// box. Callers that need to look up several different pieces of text in the same screenshot
// (e.g. a header, a last name, and a first name all in one patient-list crop) should call this
// once and match against the result multiple times with matchWordsForText, rather than paying
// for a separate billed Vision API call per text target.
export async function getOcrWords(imageBase64: string): Promise<OcrWord[]> {
  const form = new FormData();
  form.append("file", Buffer.from(imageBase64, "base64"), {
    filename: "screenshot.png",
    contentType: "image/png",
  });

  try {
    const resp = await axios.post<{ words: OcrWord[] }>("http://localhost:5003/extract/words", form, {
      headers: form.getHeaders(),
      maxBodyLength: Infinity,
      maxContentLength: Infinity,
      timeout: 30000,
    });
    return resp.data?.words ?? [];
  } catch (err: any) {
    const status = err?.response?.status;
    const detail = err?.response?.data?.detail || err?.message || "Unknown error";
    throw new Error(`OCR request failed${status ? ` (${status})` : ""}: ${detail}`);
  }
}

// Finds every occurrence of exact text within an already-OCR'd word list. Google Vision's word
// bounding boxes give an exact pixel center, so unlike locateAllOnScreenshot's AI-estimated
// top/bottom edges, there's no systematic bias to correct for.
//
// `text` may be multiple words (e.g. "Single click") — Google Vision reports one bounding box
// per individual word, not per phrase, so a multi-word target is matched as a run of consecutive
// words in the OCR result (which preserves reading order) and their boxes are merged into one.
export function matchWordsForText(words: OcrWord[], text: string): { x: number; y: number }[] {
  const targetTokens = text.trim().toLowerCase().split(/\s+/).filter(Boolean);
  if (targetTokens.length === 0) return [];

  const matches: { x: number; y: number }[] = [];
  for (let i = 0; i <= words.length - targetTokens.length; i++) {
    const span = words.slice(i, i + targetTokens.length);
    const matchesHere = span.every((w, j) => w.text.trim().toLowerCase() === targetTokens[j]);
    if (!matchesHere) continue;

    const left = Math.min(...span.map((w) => w.left));
    const top = Math.min(...span.map((w) => w.top));
    const right = Math.max(...span.map((w) => w.left + w.w));
    const bottom = Math.max(...span.map((w) => w.top + w.h));
    matches.push({ x: Math.round((left + right) / 2), y: Math.round((top + bottom) / 2) });
  }
  return matches;
}

// Convenience wrapper for callers that only need a single text lookup per screenshot (one OCR
// call, one match pass) — e.g. the Save button step, which only ever searches for one thing.
export async function locateAllViaOcr(imageBase64: string, text: string): Promise<{ x: number; y: number }[]> {
  const words = await getOcrWords(imageBase64);
  return matchWordsForText(words, text);
}
