/**
 * Apply a sequence of patch operations (the tool_use calls returned by
 * `refineLearningContent`) to an article object, in order. The returned
 * article is a fresh object — the input is not mutated.
 *
 * Recognised tool names mirror `llmTools.js`:
 *   set_intro, set_section, add_section, remove_section, replace_takeaways.
 *
 * Unknown tool names are ignored on purpose; the caller validates the
 * result against `learningArticleSchema` and rejects the whole turn if
 * the patches produced an invalid article.
 */

import { learningArticleSchema } from './llmSchemas';

function matchesHeading(section, heading) {
  return (section.heading ?? '').trim().toLowerCase() === heading.trim().toLowerCase();
}

function cloneArticle(article) {
  return {
    ...article,
    sections: article.sections.map((s) => ({ ...s })),
    keyTakeaways: [...article.keyTakeaways],
  };
}

export function applyArticlePatches(article, toolUses) {
  let next = cloneArticle(article);
  for (const tu of toolUses) {
    switch (tu.name) {
      case 'set_intro':
        next = { ...next, intro: tu.input.intro };
        break;
      case 'set_section': {
        const idx = next.sections.findIndex((s) => matchesHeading(s, tu.input.heading));
        if (idx === -1) {
          // No matching section — fall back to appending so the model's
          // intent (provide that body) is preserved rather than lost.
          next.sections = [...next.sections, { heading: tu.input.heading, body: tu.input.body }];
        } else {
          next.sections = next.sections.map((s, i) => (i === idx ? { ...s, body: tu.input.body } : s));
        }
        break;
      }
      case 'add_section': {
        const newSection = { heading: tu.input.heading, body: tu.input.body };
        next.sections = tu.input.position === 'start'
          ? [newSection, ...next.sections]
          : [...next.sections, newSection];
        break;
      }
      case 'remove_section':
        next.sections = next.sections.filter((s) => !matchesHeading(s, tu.input.heading));
        break;
      case 'replace_takeaways':
        next = { ...next, keyTakeaways: [...tu.input.items] };
        break;
      default:
        // Unknown patch op — ignore.
        break;
    }
  }
  return next;
}

/**
 * Apply the patches and re-validate against the article schema. Throws
 * a clear error if the result is invalid.
 */
export function applyAndValidate(article, toolUses) {
  const updated = applyArticlePatches(article, toolUses);
  const parsed = learningArticleSchema.safeParse({ article: updated });
  if (!parsed.success) {
    const err = new Error(`Refinement produced an invalid article: ${parsed.error.message}`);
    err.cause = parsed.error;
    throw err;
  }
  return parsed.data.article;
}
