// Outils d'accès DIRECT au matériel de cours pour le modèle (function calling) :
// navigation déterministe par séance et par diapositive + recherche plein texte FTS5 —
// aucun recours à la base vectorielle. Chaque contenu servi reçoit une balise [Sx]
// enregistrée dans le contexte, donc le contrat de citations reste strictement validé.

import { all, get } from "../db/index.ts";
import type { ContextBlock, RetrievedChunk } from "./search.ts";

export type ToolDef = {
  type: "function";
  function: { name: string; description: string; parameters: Record<string, unknown> };
};

export function courseTools(courseCode: string): ToolDef[] {
  return [
    {
      type: "function",
      function: {
        name: "lister_seances",
        description: `Liste les 14 séances du cours ${courseCode} avec leur titre et leur nombre de diapositives. À utiliser pour t'orienter avant de lire une séance.`,
        parameters: { type: "object", properties: {}, required: [] },
      },
    },
    {
      type: "function",
      function: {
        name: "plan_seance",
        description:
          "Donne le plan détaillé d'une séance : ses sections et le titre de chaque diapositive avec son numéro. À utiliser pour repérer les diapositives pertinentes avant de les lire.",
        parameters: {
          type: "object",
          properties: {
            semaine: { type: "integer", minimum: 1, maximum: 14, description: "Numéro de la séance (1 à 14)" },
          },
          required: ["semaine"],
        },
      },
    },
    {
      type: "function",
      function: {
        name: "lire_diapositives",
        description:
          "Lit le CONTENU COMPLET d'une plage de diapositives d'une séance (maximum 12 à la fois). Chaque diapositive reçoit une balise [Sx] que tu dois citer.",
        parameters: {
          type: "object",
          properties: {
            semaine: { type: "integer", minimum: 1, maximum: 14 },
            de: { type: "integer", minimum: 1, description: "Première diapositive de la plage" },
            a: { type: "integer", minimum: 1, description: "Dernière diapositive de la plage (incluse)" },
          },
          required: ["semaine", "de", "a"],
        },
      },
    },
    {
      type: "function",
      function: {
        name: "rechercher_cours",
        description:
          "Recherche plein texte dans TOUT le matériel officiel du cours (diapositives, ateliers, aide-mémoire, glossaire). Retourne les extraits les plus pertinents avec leur balise [Sx]. À utiliser quand tu ne sais pas dans quelle séance chercher.",
        parameters: {
          type: "object",
          properties: {
            requete: { type: "string", minLength: 2, maxLength: 200, description: "Mots-clés en français" },
          },
          required: ["requete"],
        },
      },
    },
  ];
}

type ChunkRow = RetrievedChunk;

const CHUNK_SELECT = `SELECT c.id, c.document_id, c.course_code, c.space, c.ref_type, c.ref_number, c.ref_label,
        c.section_title, c.title, c.content, c.box_types, c.week,
        d.title as doc_title, d.path as doc_path, d.filename, 1.0 as score
 FROM chunks c JOIN documents d ON d.id = c.document_id`;

/** Enregistre un fragment dans le contexte partagé et retourne sa balise [Sx]. */
function registerSource(context: ContextBlock, chunk: ChunkRow): string {
  const existing = context.sources.find((s) => s.chunk.id === chunk.id);
  if (existing) return existing.tag;
  const tag = `S${context.sources.length + 1}`;
  context.sources.push({ tag, chunk });
  return tag;
}

function ftsQuery(query: string): string {
  const tokens = query
    .toLowerCase()
    .replace(/[«»"'’()\[\]{}:;,!?<>=+*/\\^~`|-]/g, " ")
    .split(/\s+/)
    .filter((t) => t.length > 1)
    .slice(0, 12);
  if (!tokens.length) return '""';
  return tokens.map((t) => `"${t}"`).join(" OR ");
}

/**
 * Exécute un outil de cours. `context` est le bloc de sources PARTAGÉ de la conversation :
 * tout contenu servi y est enregistré, ce qui rend ses balises citables et validées.
 */
export function executeCourseTool(
  name: string,
  rawArgs: string,
  courseCode: string,
  context: ContextBlock
): string {
  let args: Record<string, unknown> = {};
  try {
    args = rawArgs ? JSON.parse(rawArgs) : {};
  } catch {
    return "Erreur : arguments JSON invalides.";
  }
  const space = `official-${courseCode.toLowerCase()}`;

  if (name === "lister_seances") {
    const decks = all<{ week: number; title: string; n: number }>(
      `SELECT d.week, d.title, COUNT(c.id) as n FROM documents d JOIN chunks c ON c.document_id = d.id
       WHERE d.course_code = ? AND d.doc_type = 'slides' AND d.visible_to_students = 1
       GROUP BY d.id ORDER BY d.week`,
      courseCode
    );
    if (!decks.length) return "Aucune séance indexée.";
    return decks.map((d) => `Séance ${d.week} — ${d.title} (${d.n} diapositives)`).join("\n");
  }

  if (name === "plan_seance") {
    const week = Number(args.semaine);
    if (!Number.isInteger(week) || week < 1 || week > 14) return "Erreur : semaine invalide (1-14).";
    const rows = all<{ ref_number: number; title: string; section_title: string }>(
      `SELECT c.ref_number, c.title, c.section_title FROM chunks c JOIN documents d ON d.id = c.document_id
       WHERE d.course_code = ? AND d.doc_type = 'slides' AND d.week = ? AND d.visible_to_students = 1
       ORDER BY c.ref_number, c.seq`,
      courseCode, week
    );
    if (!rows.length) return `Aucune diapositive pour la séance ${week}.`;
    const lines: string[] = [];
    let section = " ";
    for (const r of rows) {
      if (r.section_title !== section) {
        section = r.section_title;
        if (section) lines.push(`\n## ${section}`);
      }
      lines.push(`${r.ref_number}. ${r.title}`);
    }
    return `Plan de la séance ${week} (${rows.length} diapositives) :${lines.join("\n")}\n\nUtilise lire_diapositives pour lire une plage précise.`;
  }

  if (name === "lire_diapositives") {
    const week = Number(args.semaine);
    let from = Number(args.de);
    let to = Number(args.a);
    if (!Number.isInteger(week) || week < 1 || week > 14) return "Erreur : semaine invalide (1-14).";
    if (!Number.isInteger(from) || !Number.isInteger(to) || from < 1 || to < from) return "Erreur : plage invalide.";
    if (to - from + 1 > 12) to = from + 11;
    const rows = all<ChunkRow>(
      `${CHUNK_SELECT} WHERE d.course_code = ? AND d.doc_type = 'slides' AND d.week = ?
         AND c.ref_number >= ? AND c.ref_number <= ? AND d.visible_to_students = 1
       ORDER BY c.ref_number, c.seq`,
      courseCode, week, from, to
    );
    if (!rows.length) return `Aucune diapositive ${from}-${to} pour la séance ${week}.`;
    return rows
      .map((r) => {
        const tag = registerSource(context, r);
        return `[${tag}] (Séance ${week} — Diapositive ${r.ref_number} — « ${r.title} »)\n${r.content.slice(0, 2400)}`;
      })
      .join("\n\n---\n\n");
  }

  if (name === "rechercher_cours") {
    const q = String(args.requete ?? "").trim();
    if (q.length < 2) return "Erreur : requête trop courte.";
    const hits = all<{ rowid: number }>(
      `SELECT f.rowid as rowid FROM chunks_fts f JOIN chunks c ON c.id = f.rowid
       WHERE chunks_fts MATCH ? AND c.space = ?
       ORDER BY bm25(chunks_fts, 3.0, 1.0) LIMIT 8`,
      ftsQuery(q), space
    );
    if (!hits.length) return `Aucun résultat pour « ${q} » dans le matériel officiel.`;
    const rows = all<ChunkRow>(
      `${CHUNK_SELECT} WHERE c.id IN (${hits.map(() => "?").join(",")})`,
      ...hits.map((h) => h.rowid)
    );
    const ordered = hits.map((h) => rows.find((r) => r.id === h.rowid)).filter((r): r is ChunkRow => !!r);
    return ordered
      .map((r) => {
        const tag = registerSource(context, r);
        return `[${tag}] (${r.doc_title}${r.ref_label ? " — " + r.ref_label : ""} — « ${r.title} »)\n${r.content.slice(0, 1200)}`;
      })
      .join("\n\n---\n\n");
  }

  return `Erreur : outil inconnu « ${name} ».`;
}
