/**
 * officeTool — single Office tool covering Word (.docx), Excel (.xlsx),
 * and PowerPoint (.pptx). Replaces the older `doc` and `spreadsheet`
 * tools (which we keep registered for back-compat for one release).
 *
 * Action shape — plural-canonical envelope:
 *
 *   {
 *     toolId: "office",
 *     actions: [
 *       { type: "doc.create",   outputPath, content },
 *       { type: "doc.read",     filePath, format? },
 *       { type: "doc.info",     filePath },
 *       { type: "sheet.create", outputPath, content },
 *       { type: "sheet.read",   filePath, sheetName?, startRow?, endRow?, includeFormulas? },
 *       { type: "sheet.info",   filePath },
 *       { type: "pres.create",  outputPath, content },
 *       { type: "pres.info",    filePath }
 *     ]
 *   }
 *
 * Forgiving inputs:
 *   - Accepts both `type` and `action` as the discriminator.
 *   - Accepts a legacy singular envelope `{action, ...}` for one release
 *     cycle (reverse-forgiveness pattern). New callers should generate
 *     the plural envelope.
 *
 * Output:
 *   - Each action returns {success, action, ...payload, output, message}.
 *   - Multiple actions in one envelope return `{success, summary, results}`
 *     similar to the bulk-agent endpoint.
 */

import { BaseTool } from './baseTool.js';
import TagParser from '../utilities/tagParser.js';
import fs from 'fs/promises';
import path from 'path';

import { createDoc, readDoc, docInfo } from './office/officeDoc.js';
import { createSheet, readSheet, sheetInfo } from './office/officeSheet.js';
import {
  createPres,
  presInfo,
  readPresOutline,
  fillPresPlaceholders,
  listPresentationSystem,
} from './office/officePres.js';

const SUPPORTED = new Set([
  'doc.create', 'doc.read', 'doc.info',
  'sheet.create', 'sheet.read', 'sheet.info',
  'pres.create', 'pres.info', 'pres.read', 'pres.fill', 'pres.system',
]);

const READ_ACTIONS = new Set([
  'doc.read', 'doc.info',
  'sheet.read', 'sheet.info',
  'pres.info', 'pres.read', 'pres.system',
]);
const CREATE_ACTIONS = new Set(['doc.create', 'sheet.create', 'pres.create', 'pres.fill']);

class OfficeTool extends BaseTool {
  constructor(config = {}, logger = null) {
    super(config, logger);
    this.id = 'office';
    this.name = 'Office Tool';
    this.description = 'Create + read Word (.docx), Excel (.xlsx), and PowerPoint (.pptx) files';
    this.version = '1.0.0';
    this.requiresProject = false;
    this.isAsync = false;
  }

  getDescription() {
    return `
Office Tool: Create and read Word, Excel, and PowerPoint files in one place.

Plural-canonical envelope:

\`\`\`json
{
  "toolId": "office",
  "actions": [
    { "type": "doc.create",   "outputPath": "out/report.docx",     "content": { "title": "...", "sections": [ { "children": [ ... ] } ] } },
    { "type": "sheet.create", "outputPath": "out/data.xlsx",       "content": { "sheets": [ { "name": "Q1", "columns": [...], "rows": [...] } ] } },
    { "type": "pres.create",  "outputPath": "out/deck.pptx",       "content": { "title": "...", "slides": [ { "layout": "title", "title": "..." } ] } },
    { "type": "doc.read",     "filePath":   "in/notes.docx",       "format": "text" },
    { "type": "sheet.read",   "filePath":   "in/data.xlsx",        "sheetName": "Q1", "startRow": 1, "endRow": 50 },
    { "type": "doc.info",     "filePath":   "in/notes.docx" },
    { "type": "sheet.info",   "filePath":   "in/data.xlsx" },
    { "type": "pres.info",    "filePath":   "in/deck.pptx" }
  ]
}
\`\`\`

Word (.docx) content nodes:
  - heading      { level: 1-6, text, color?, alignment? }
  - paragraph    { text, bold?, italic?, underline?, fontSize?, color?, alignment?, indent? }
  - richParagraph{ runs: [{ text, bold?, italic?, color?, size?, font? }] }
  - list         { ordered?: bool, items: ["..."] or [{ text, level: 0-3 }] }
  - table        { headers: [...], rows: [[...]], widths?, headerFill?, alternateFill?, borderColor? }
  - callout      { label, content, color? }   — left-bar coloured callout
  - hyperlink    { text, url }
  - pageBreak / horizontalRule

Excel (.xlsx) sheet object:
  - columns      [{ header, key?, width?, type: "text|number|currency|percent|date|duration", numFmt? }]
  - rows         [ { key: val } | [val, ...] ]   — strings starting with "=" become formulas
  - headerStyle  { bold?, fontColor?, fill?, alignment?, height? }
  - alternateRowFill   #RRGGBB for zebra rows
  - formulas     [{ cell: "C2", formula: "=A2+B2" }]
  - cellStyles   [{ cell: "B5", bold?, fill?, fontColor?, alignment?, border? }]
  - mergedCells  [{ range: "A1:C1" }]
  - freezeRow / freezeColumn
  - autoFilter   true | { from, to }
  - conditionalFormatting [{ range, type: "cellIs|containsText|dataBar|colorScale", operator?, value?, fill?, fontColor? }]
  - namedRanges  [{ name, range }]
  - charts       [...]   — best-effort

PowerPoint (.pptx) — TWO ways to build, pick by intent:

  ═══ A) THEMED (recommended — looks designed) ═══════════════════════
  Pass a STRING \`theme\` and pick an archetype per slide. The system
  handles colour, typography, spacing, backgrounds, chart styling so
  the deck looks like a designer made it. Use this 95% of the time.

  Example:
  {
    "type": "pres.create",
    "outputPath": "out/q4-report.pptx",
    "content": {
      "title": "Q4 2026 Investor Update",
      "theme": "tech-bold",
      "palette": "electric-night",       // optional (per-theme default if omitted)
      "spacing": "cozy",                  // optional: tight | cozy | airy
      "shape": "round",                   // optional: sharp | soft | round | pill
      "background": "mesh",               // optional: solid | gradient | mesh | noise | geometric | grid
      "slides": [
        { "archetype": "hero",          "eyebrow": "Q4 2026", "title": "Higher gear.", "subtitle": "Investor update", "author": "Loxia", "date": "May 28" },
        { "archetype": "agenda",        "items": ["Where we are", "What changed", "What's next"] },
        { "archetype": "sectionHeader", "number": "01", "title": "Where we are", "subtitle": "Growth, retention, runway" },
        { "archetype": "statCallout",   "eyebrow": "ARR growth", "stat": "+47%", "label": "Year-over-year", "footnote": "Through April 2026" },
        { "archetype": "twoColChart",   "title": "Revenue mix", "leftText": { "headline": "Composition shifted", "body": "..." }, "chart": { "type": "donut", "data": [ { "label": "Pro", "value": 62 }, { "label": "Team", "value": 28 }, { "label": "Enterprise", "value": 10 } ] } },
        { "archetype": "bullets",       "title": "Key wins",  "items": ["Shipped X", "Closed Y", "Hired Z"] },
        { "archetype": "twoColText",    "title": "Risks & mitigations", "left": { "headline": "Risk", "body": "..." }, "right": { "headline": "Mitigation", "body": "..." } },
        { "archetype": "quote",         "quote": "...",        "attribution": "Customer name, role" },
        { "archetype": "table",         "title": "By segment", "headers": ["Segment", "ARR", "Net retention"], "rows": [["Pro", "$4.1M", "118%"], ["Team", "$1.8M", "133%"]] },
        { "archetype": "closing",       "eyebrow": "Thank you", "title": "Let's keep building.", "cta": { "label": "loxia.ai/q4", "url": "https://loxia.ai/q4" } }
      ]
    }
  }

  Themes (pick by use case):
    - "editorial-warm" — narrative/research/brand decks (serif display + sans body, warm cream bg, sharp corners)
    - "tech-bold"      — investor pitches, product launches (dark, electric accents, mesh backgrounds, round corners + shadows)
    - "data-heavy"     — KPI reviews, analyst reports (dense grid, monospace numerals, restrained colours)

  Palettes (preset names — every theme has 3):
    - editorial-warm:  cream-charcoal · ink-paper · mint-charcoal
    - tech-bold:       electric-night · cyber-dawn · neon-violet
    - data-heavy:      bloomberg · mono-data · finch

  Archetypes (slide types — pick per slide):
    - hero           — opening slide:  { eyebrow?, title, subtitle?, author?, date? }
    - agenda         — overview list:  { title?, items: [string | {title, hint}] }
    - sectionHeader  — section break:  { number?, title, subtitle? }
    - statCallout    — big stat:       { eyebrow?, stat, label, footnote? }
    - bullets        — title + list:   { title?, subtitle?, items }
    - twoColText     — side-by-side:   { title?, left: {headline, body}, right: {headline, body} }
    - twoColChart    — text + chart:   { title?, leftText: {headline, body}, chart: {...} }
    - quote          — pull-quote:     { quote, attribution?, subtext? }
    - imageFullBleed — full image:     { imagePath | imageData, title?, eyebrow?, overlay? }
    - table          — data table:     { title?, headers, rows }
    - closing        — last slide:     { eyebrow?, title, cta?, contact? }
    - freeform       — escape hatch:   { title?, items: [{type, x, y, w, h, ...}] } — coordinates as 0..1 of safe area

  Chart spec inside twoColChart or freeform:
    { type: "bar|column|line|area|pie|donut", categories?: [...], series?: [{name, data:[...]}], data?: [{label, value}], stacked?: bool }

  ═══ B) LEGACY (manual coordinates — only for fine control) ═══════════
  Omit \`theme\` (or pass it as an object) and you get the pre-themed
  builder where you place each element yourself. Use this only when
  you genuinely need to draw something the archetypes can't:

  {
    "type": "pres.create",
    "outputPath": "out/deck.pptx",
    "content": {
      "title": "...",
      "theme": { "fontFamily": "Calibri", "primaryColor": "#1F3864" },
      "slides": [ { "layout": "content", "title": "...", "content": [ { "type": "text", "text": "...", "x": 0.5, "y": 1, "w": 9, "h": 0.7 } ] } ]
    }
  }

  Discover available themes / archetypes / backgrounds at runtime:
    { "type": "pres.system" }   → { themes, archetypes, backgrounds }

  Read or fill an existing .pptx:
    { "type": "pres.read", "filePath": "in/template.pptx" }
        → { slideCount, slides: [{index, title, body, tokens}], tokens }
    { "type": "pres.fill", "sourcePath": "in/template.pptx", "outputPath": "out/customised.pptx",
      "values": { "customerName": "Acme", "date": "May 2026", ... } }
        → { path, size, replaced: {token: count}, unresolved }
    (Templates use {{tokenName}} placeholders. pres.read first to discover them.)

Output paths can be relative — they're resolved under the agent's projectDir
and protected against path traversal.
    `.trim();
  }

  parseParameters(content) {
    // Backwards compatibility for tag-format. Most callers use JSON.
    try {
      const typeMatches = TagParser.extractContent(content, 'type');
      const actionMatches = TagParser.extractContent(content, 'action');
      const filePathMatches = TagParser.extractContent(content, 'filePath');
      const outputPathMatches = TagParser.extractContent(content, 'outputPath');
      const formatMatches = TagParser.extractContent(content, 'format');
      return {
        actions: [{
          type: (typeMatches[0] || actionMatches[0] || 'doc.info').trim(),
          filePath: (filePathMatches[0] || '').trim(),
          outputPath: (outputPathMatches[0] || '').trim(),
          format: (formatMatches[0] || 'text').trim(),
        }],
      };
    } catch (error) {
      throw new Error(`Failed to parse Office tool parameters: ${error.message}`);
    }
  }

  getSupportedActions() {
    return Array.from(SUPPORTED);
  }

  async execute(params, context) {
    const { projectDir } = context || {};

    // Normalise envelope: accept plural-canonical OR legacy singular.
    let actions = Array.isArray(params?.actions) ? params.actions : null;
    if (!actions) {
      const single = params?.action ? { ...params, type: params.action } : params;
      if (single && (single.type || single.action)) actions = [single];
    }
    if (!actions || actions.length === 0) {
      return {
        success: false,
        error: 'No actions provided',
        output: 'Provide actions array — e.g. [{ "type": "doc.create", "outputPath": "...", "content": { ... } }]',
      };
    }

    const results = [];
    for (const raw of actions) {
      const type = String(raw.type || raw.action || '').toLowerCase();
      if (!SUPPORTED.has(type)) {
        results.push({
          success: false,
          type,
          error: `Unknown action: ${type}`,
          output: `Unsupported action "${type}". Supported: ${Array.from(SUPPORTED).join(', ')}`,
        });
        continue;
      }
      try {
        const result = await this._runOne(type, raw, projectDir);
        results.push(result);
      } catch (err) {
        this.logger?.error?.('officeTool action failed', { type, error: err.message });
        results.push({ success: false, type, error: err.message, output: err.message });
      }
    }

    // Single-action: return its result directly (matches the legacy shape).
    if (results.length === 1) return results[0];

    const ok = results.filter(r => r.success).length;
    const failed = results.length - ok;
    return {
      success: failed === 0,
      summary: { total: results.length, ok, failed },
      results,
      output: `Office: ${ok}/${results.length} actions succeeded`,
    };
  }

  async _runOne(type, action, projectDir) {
    if (CREATE_ACTIONS.has(type)) {
      return this._handleCreate(type, action, projectDir);
    }
    if (READ_ACTIONS.has(type)) {
      return this._handleRead(type, action, projectDir);
    }
    return { success: false, type, error: 'Unhandled action', output: `Unhandled: ${type}` };
  }

  async _handleCreate(type, action, projectDir) {
    let outputPath = action.outputPath;
    if (!outputPath) {
      return { success: false, type, error: 'outputPath is required', output: 'Provide an outputPath' };
    }
    // pres.fill takes `values` + `sourcePath` instead of `content`; skip
    // the content check for that single action.
    if (type !== 'pres.fill' && (!action.content || typeof action.content !== 'object')) {
      return { success: false, type, error: 'content is required', output: 'Provide a content object' };
    }

    if (!path.isAbsolute(outputPath)) {
      outputPath = path.resolve(projectDir || process.cwd(), outputPath);
    }
    const baseDir = projectDir || process.cwd();
    const normalized = path.normalize(outputPath);
    if (!normalized.startsWith(path.normalize(baseDir))) {
      return { success: false, type, error: 'Path traversal detected', output: 'outputPath must be inside the project directory' };
    }
    await fs.mkdir(path.dirname(normalized), { recursive: true });

    // Ensure extension matches the action type.
    const extExpected = type === 'doc.create' ? '.docx'
      : type === 'sheet.create' ? '.xlsx'
      : '.pptx';
    let finalPath = normalized;
    if (!finalPath.toLowerCase().endsWith(extExpected)) {
      finalPath += extExpected;
    }

    let writeResult;
    if (type === 'doc.create')   writeResult = await createDoc(action.content, finalPath);
    if (type === 'sheet.create') writeResult = await createSheet(action.content, finalPath);
    if (type === 'pres.create')  writeResult = await createPres(action.content, finalPath);
    if (type === 'pres.fill') {
      // pres.fill takes sourcePath + values instead of `content`.
      // The path-resolution above already handled outputPath; resolve
      // sourcePath the same way before forwarding.
      let sourcePath = action.sourcePath;
      if (!sourcePath) {
        return { success: false, type, error: 'sourcePath is required', output: 'pres.fill needs sourcePath (a template .pptx)' };
      }
      if (!path.isAbsolute(sourcePath)) sourcePath = path.resolve(projectDir || process.cwd(), sourcePath);
      writeResult = await fillPresPlaceholders({
        sourcePath,
        outputPath: finalPath,
        values: action.values || {},
      });
    }

    return {
      success: true,
      type,
      outputPath: writeResult.path,
      fileSize: writeResult.size,
      output: `Created ${path.basename(writeResult.path)} (${(writeResult.size / 1024).toFixed(1)} KB)`,
      message: `Wrote ${path.basename(writeResult.path)}`,
      ...(writeResult.sheetCount  ? { sheetCount:  writeResult.sheetCount  } : {}),
      ...(writeResult.slideCount  ? { slideCount:  writeResult.slideCount  } : {}),
      ...(writeResult.theme       ? { theme:       writeResult.theme       } : {}),
      ...(writeResult.palette     ? { palette:     writeResult.palette     } : {}),
      ...(writeResult.replaced    ? { replaced:    writeResult.replaced    } : {}),
      ...(writeResult.unresolved  ? { unresolved:  writeResult.unresolved  } : {}),
    };
  }

  async _handleRead(type, action, projectDir) {
    // pres.system is a pure metadata query — no file involved.
    if (type === 'pres.system') {
      const system = listPresentationSystem();
      return {
        success: true,
        type,
        ...system,
        output:
          `Themes: ${system.themes.map(t => t.id).join(', ')}\n` +
          `Archetypes: ${system.archetypes.join(', ')}\n` +
          `Backgrounds: ${system.backgrounds.join(', ')}`,
      };
    }

    let filePath = action.filePath;
    if (!filePath) {
      return { success: false, type, error: 'filePath is required', output: 'Provide a filePath' };
    }
    if (!path.isAbsolute(filePath)) {
      filePath = path.resolve(projectDir || process.cwd(), filePath);
    }
    try {
      await fs.access(filePath);
    } catch {
      return { success: false, type, error: 'File not found', output: `Not found: ${filePath}` };
    }
    // Extension guards
    const lower = filePath.toLowerCase();
    if ((type === 'doc.read' || type === 'doc.info') && !/\.docx?$/.test(lower)) {
      return { success: false, type, error: 'Wrong extension', output: `Expected .docx for ${type}` };
    }
    if ((type === 'sheet.read' || type === 'sheet.info') && !/\.(xlsx|xls|csv)$/.test(lower)) {
      return { success: false, type, error: 'Wrong extension', output: `Expected .xlsx/.csv for ${type}` };
    }
    if ((type === 'pres.info' || type === 'pres.read') && !/\.pptx$/.test(lower)) {
      return { success: false, type, error: 'Wrong extension', output: `Expected .pptx for ${type}` };
    }

    if (type === 'doc.info') {
      const info = await docInfo(filePath);
      return { success: true, type, filePath, info, output: `Doc info: ${info.fileName} (${info.wordCount} words, ~${info.estimatedPages} pages)` };
    }
    if (type === 'doc.read') {
      const r = await readDoc(filePath, action.format || 'text');
      return { success: true, type, filePath, format: r.format, content: r.content, output: r.content };
    }
    if (type === 'sheet.info') {
      const info = await sheetInfo(filePath);
      const summary = info.sheets.map(s => `${s.name} (${s.rowCount}r × ${s.columnCount}c)`).join('; ');
      return { success: true, type, filePath, info, output: `Sheet info: ${info.fileName} — ${summary}` };
    }
    if (type === 'sheet.read') {
      const r = await readSheet(filePath, action);
      return {
        success: true, type, filePath,
        sheetName: r.sheetName, totalRows: r.totalRows, rowCount: r.rows.length, rows: r.rows,
        output: `Read ${r.rows.length} rows from "${r.sheetName}"`,
      };
    }
    if (type === 'pres.info') {
      const info = await presInfo(filePath);
      return { success: true, type, filePath, info, output: `Pres info: ${info.fileName} (${info.slideCount ?? '?'} slides)` };
    }
    if (type === 'pres.read') {
      const outline = await readPresOutline(filePath);
      return {
        success: true, type, filePath,
        slideCount: outline.slideCount,
        slides: outline.slides,
        tokens: outline.tokens,
        output:
          `${outline.slideCount} slides${outline.tokens.length ? `; tokens: ${outline.tokens.join(', ')}` : ''}`,
      };
    }
    return { success: false, type, error: 'Unhandled', output: `Unhandled: ${type}` };
  }
}

export default OfficeTool;
