import { promises as fs } from 'fs';
import * as path from 'path';
import { createHash } from 'crypto';
import type { DocSource } from './markdown.extractor';

/**
 * Extrae el contenido de componentes <ScreenGuia> / <MarangatuGuia> / <RRHHGuia>
 * del frontend `pos-ventas` usando una estrategia regex robusta (sin Babel para
 * evitar la dependencia adicional). Captura `titulo` y `pasos` cuando son
 * literales o arrays JSON.
 *
 * No es exhaustivo: si la prop viene de una variable externa o de un import,
 * la salta. Igual cubre la mayoría de los casos que están escritos inline.
 */
export async function extraerScreenGuias(frontendRoot: string): Promise<DocSource[]> {
  const componentsDir = path.join(frontendRoot, 'src', 'components');
  const archivos: string[] = [];
  await walk(componentsDir, archivos);

  const docs: DocSource[] = [];
  for (const abs of archivos) {
    if (!abs.endsWith('.jsx') && !abs.endsWith('.tsx')) continue;
    const src = await fs.readFile(abs, 'utf8');
    if (!/<(ScreenGuia|MarangatuGuia|RRHHGuia|RevisionCxCGuia)\b/.test(src)) continue;

    const md = construirMarkdown(src);
    if (!md) continue;

    const rel = path.relative(frontendRoot, abs);
    const titulo = extraerTituloProp(src) || derivarTituloDePath(rel);
    docs.push({
      origen_path: rel,
      titulo,
      contenido_md: md,
      screen_key: derivarScreenKey(rel),
      version_hash: hashContenido(md),
      audiencia: 'usuario',
      aliases: [],
    });
  }
  return docs;
}

async function walk(dir: string, out: string[]) {
  let entries: import('fs').Dirent[];
  try {
    entries = await fs.readdir(dir, { withFileTypes: true });
  } catch {
    return;
  }
  for (const e of entries) {
    const full = path.join(dir, e.name);
    if (e.isDirectory()) {
      if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
      await walk(full, out);
    } else if (e.isFile()) {
      out.push(full);
    }
  }
}

function extraerTituloProp(src: string): string | null {
  const m = src.match(/<(?:ScreenGuia|MarangatuGuia|RRHHGuia|RevisionCxCGuia)\b[^>]*\btitulo\s*=\s*['"`]([^'"`]+)['"`]/);
  if (m) return m[1].trim();
  return null;
}

function derivarTituloDePath(rel: string): string {
  return path.basename(rel, path.extname(rel));
}

function derivarScreenKey(rel: string): string {
  // src/components/tesoreria/TesChequesTab.jsx → tesoreria/cheques
  const norm = rel.replace(/\\/g, '/');
  const sin = norm.replace(/^src\/components\//, '').replace(/\.(jsx|tsx)$/i, '');
  return sin
    .split('/')
    .map((p) =>
      p
        .replace(/^(Tes|Rrhh|Marangatu|Cobros|Pos|Factura|Stock|Compra|Pago|Prov|Cli|Cxc)/i, '')
        .replace(/Tab$|Template$|Panel$|Dialog$|Form$/i, '')
        .replace(/^[A-Z]/, (c) => c.toLowerCase()),
    )
    .filter(Boolean)
    .join('/');
}

function hashContenido(s: string): string {
  return createHash('sha256').update(s).digest('hex');
}

/**
 * Construye un markdown a partir del JSX de la guía. Estrategia:
 *   1. Extraer `titulo` (string literal).
 *   2. Buscar `pasos={[ ... ]}` y dentro los objetos con `titulo`, `desc`/`descripcion`, `nota`.
 *   3. Buscar `notas={[ ... ]}` con strings.
 *   4. Si nada de eso aparece, devolver null.
 */
function construirMarkdown(src: string): string | null {
  const titulo = extraerTituloProp(src);
  const partes: string[] = [];
  if (titulo) partes.push(`# ${titulo}`);

  const pasos = extraerArrayProp(src, 'pasos');
  if (pasos) {
    partes.push('\n## Pasos');
    for (const obj of pasos) {
      const t = obj.titulo ?? obj.title ?? obj.label ?? '';
      const d = obj.desc ?? obj.descripcion ?? obj.description ?? obj.detalle ?? '';
      const nota = obj.nota ?? obj.note ?? '';
      if (t) partes.push(`\n### ${t}`);
      if (d) partes.push(d);
      if (nota) partes.push(`\n> ${nota}`);
    }
  }

  const notas = extraerArrayStrings(src, 'notas');
  if (notas && notas.length > 0) {
    partes.push('\n## Notas');
    for (const n of notas) partes.push(`- ${n}`);
  }

  const erroresComunes = extraerArrayStrings(src, 'erroresComunes');
  if (erroresComunes && erroresComunes.length > 0) {
    partes.push('\n## Errores comunes');
    for (const e of erroresComunes) partes.push(`- ${e}`);
  }

  if (partes.length <= 1) return null;
  return partes.join('\n');
}

function extraerArrayProp(src: string, propName: string): Array<Record<string, string>> | null {
  // Busca: propName={[ ... ]}  matching balanceado simple
  const idx = src.indexOf(`${propName}={`);
  if (idx < 0) return null;
  const start = src.indexOf('[', idx);
  if (start < 0) return null;
  const end = encontrarCierreCorchete(src, start);
  if (end < 0) return null;
  const body = src.slice(start + 1, end);
  return parsearArrayDeObjetos(body);
}

function extraerArrayStrings(src: string, propName: string): string[] | null {
  const idx = src.indexOf(`${propName}={`);
  if (idx < 0) return null;
  const start = src.indexOf('[', idx);
  if (start < 0) return null;
  const end = encontrarCierreCorchete(src, start);
  if (end < 0) return null;
  const body = src.slice(start + 1, end);
  const strings = [...body.matchAll(/['"`]([^'"`]+)['"`]/g)].map((m) => m[1].trim()).filter(Boolean);
  return strings;
}

function encontrarCierreCorchete(s: string, openIdx: number): number {
  let depth = 0;
  let inStr: string | null = null;
  for (let i = openIdx; i < s.length; i++) {
    const c = s[i];
    if (inStr) {
      if (c === '\\') {
        i++;
        continue;
      }
      if (c === inStr) inStr = null;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') {
      inStr = c;
      continue;
    }
    if (c === '[') depth++;
    else if (c === ']') {
      depth--;
      if (depth === 0) return i;
    }
  }
  return -1;
}

function parsearArrayDeObjetos(body: string): Array<Record<string, string>> {
  // Encuentra objetos { ... } a nivel raíz y dentro extrae pares key: 'string'
  const objetos: Array<Record<string, string>> = [];
  let depth = 0;
  let inStr: string | null = null;
  let startObj = -1;
  for (let i = 0; i < body.length; i++) {
    const c = body[i];
    if (inStr) {
      if (c === '\\') {
        i++;
        continue;
      }
      if (c === inStr) inStr = null;
      continue;
    }
    if (c === '"' || c === "'" || c === '`') {
      inStr = c;
      continue;
    }
    if (c === '{') {
      if (depth === 0) startObj = i;
      depth++;
    } else if (c === '}') {
      depth--;
      if (depth === 0 && startObj >= 0) {
        const objBody = body.slice(startObj + 1, i);
        objetos.push(parsearObjeto(objBody));
        startObj = -1;
      }
    }
  }
  return objetos;
}

function parsearObjeto(body: string): Record<string, string> {
  const out: Record<string, string> = {};
  // key: 'value' | "value" | `value`
  const re = /(\w+)\s*:\s*(['"`])((?:\\.|(?!\2).)*)\2/g;
  let m: RegExpExecArray | null;
  while ((m = re.exec(body)) !== null) {
    out[m[1]] = m[3].replace(/\\n/g, '\n').replace(/\\'/g, "'").replace(/\\"/g, '"');
  }
  return out;
}
