// Copyright (c) 2026 defconxt. All rights reserved.
// Licensed under AGPL-3.0 — see LICENSE file for details.

/**
 * Base handler — shared actions every domain handler inherits.
 *
 * Subclasses override domainActions() and executeDomain() to add
 * domain-specific commands on top of the common set.
 */

export class BaseHandler {
  constructor(mode) {
    this.mode = mode;
  }

  /** All available actions (common + domain-specific). */
  actions() {
    return [...this.commonActions(), ...this.domainActions()];
  }

  /** Actions shared across all modes. */
  commonActions() {
    return ['analyze', 'enumerate', 'assess', 'report'];
  }

  /** Override in subclass — domain-specific actions. */
  domainActions() {
    return [];
  }

  /** Description for help text. Override per-action in subclass. */
  describe(action) {
    const common = {
      analyze:   'Extract knowledge, tables, ATT&CK mappings from SKILL.md',
      enumerate: 'List commands and tools from api-reference.md',
      assess:    'Evaluate against technique checklist',
      report:    'Generate structured findings report',
    };
    return common[action] || this.describeDomain(action) || 'No description';
  }

  /** Override in subclass for domain-specific descriptions. */
  describeDomain(action) { return null; }

  /** Dispatch to the right action. */
  async execute(command, ctx) {
    switch (command) {
      case 'analyze':   return this.analyze(ctx);
      case 'enumerate': return this.enumerate(ctx);
      case 'assess':    return this.assess(ctx);
      case 'report':    return this.report(ctx);
      default:          return this.executeDomain(command, ctx);
    }
  }

  /** Override in subclass for domain-specific execution. */
  async executeDomain(command, ctx) {
    throw new Error(`Unknown command: ${command}`);
  }

  // ── Common actions ────────────────────────────────────────────────

  /** Analyze SKILL.md — extract all structured data. */
  async analyze(ctx) {
    const { skill, technique, domain, mode } = ctx;
    if (!skill) throw new Error('No SKILL.md found');

    // Enrich with runtime attack mappings if available
    let mappedAttackIds = skill.attackIds;
    let relatedSkills = [];
    try {
      const { getAttackIds, skillsForTechnique } = await import('../../knowledge/attack-mappings.js');
      const fromMapping = getAttackIds(domain, technique);
      if (fromMapping.length > 0) {
        // Merge: SKILL.md frontmatter + mapping JSON
        mappedAttackIds = [...new Set([...(skill.attackIds || []), ...fromMapping])];
      }
      // Find related skills that share ATT&CK techniques
      for (const id of mappedAttackIds.slice(0, 3)) {
        const related = skillsForTechnique(id).filter(s => s !== `${domain}/${technique}`);
        relatedSkills.push(...related);
      }
      relatedSkills = [...new Set(relatedSkills)].slice(0, 5);
    } catch { /* mappings not available */ }

    return {
      action: 'analyze',
      technique,
      domain,
      mode: mode.toUpperCase(),
      name: skill.frontmatter?.name || technique,
      description: skill.frontmatter?.description || '',
      tags: skill.frontmatter?.tags || [],
      attackIds: mappedAttackIds,
      relatedSkills,
      tools: skill.tools,
      sections: skill.sections.map(s => ({
        level: s.level,
        title: s.title,
        // Truncate content to avoid dumping entire SKILL.md
        summary: s.content.slice(0, 200) + (s.content.length > 200 ? '...' : ''),
      })),
      tables: skill.tables.map(t => ({
        headers: t.headers,
        rowCount: t.rows.length,
        sample: t.rows.slice(0, 3),
      })),
      codeBlockCount: skill.codeBlocks.length,
      status: 'analyze_complete',
    };
  }

  /** Enumerate commands/tools from api-reference.md. */
  enumerate(ctx) {
    const source = ctx.apiRef || ctx.skill;
    if (!source) throw new Error('No api-reference.md or SKILL.md found');

    const commands = [];

    // Extract from code blocks
    for (const block of source.codeBlocks) {
      for (const line of block.code.split('\n')) {
        const trimmed = line.trim();
        // Skip comments and empty lines
        if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('//') || trimmed.startsWith('*')) continue;
        // Skip pure Python/JS function defs — only collect executable commands
        if (trimmed.startsWith('def ') || trimmed.startsWith('function ') || trimmed.startsWith('import ')) continue;
        if (trimmed.startsWith('class ') || trimmed.startsWith('from ')) continue;
        commands.push({
          command: trimmed,
          lang: block.lang,
          context: block.context,
        });
      }
    }

    // Extract from tables (command tables)
    for (const table of source.tables) {
      const cmdHeader = table.headers.find(h =>
        /command|syntax|usage/i.test(h)
      );
      if (cmdHeader) {
        for (const row of table.rows) {
          commands.push({
            command: row[cmdHeader],
            description: row[table.headers.find(h => /description|purpose/i.test(h))] || '',
            context: 'table',
          });
        }
      }
    }

    return {
      action: 'enumerate',
      technique: ctx.technique,
      domain: ctx.domain,
      mode: ctx.mode.toUpperCase(),
      tools: source.tools,
      commandCount: commands.length,
      commands: commands.slice(0, 50), // Cap output size
      status: 'enumerate_complete',
    };
  }

  /** Assess against technique checklist. */
  assess(ctx) {
    const { skill, technique, domain, mode, flags } = ctx;
    if (!skill) throw new Error('No SKILL.md found');

    const target = flags.target || flags.scope || 'unspecified';

    // Build checklist from tables and sections
    const checklist = [];

    // Look for prerequisite tables
    for (const table of skill.tables) {
      if (table.headers.some(h => /requirement|prerequisite|control|check/i.test(h))) {
        for (const row of table.rows) {
          const item = row[table.headers[0]] || Object.values(row)[0];
          checklist.push({
            item,
            category: table.headers[0],
            status: 'needs_review',
          });
        }
      }
    }

    // Look for checklist patterns in content ([ ] and [x])
    const checklistRe = /^\s*[-*] \[([x ])\] (.+)$/gm;
    let m;
    while ((m = checklistRe.exec(skill.raw))) {
      checklist.push({
        item: m[2].trim(),
        category: 'checklist',
        status: m[1] === 'x' ? 'implemented' : 'needs_review',
      });
    }

    return {
      action: 'assess',
      technique,
      domain,
      mode: mode.toUpperCase(),
      target,
      attackIds: skill.attackIds,
      checklistItems: checklist.length,
      checklist: checklist.slice(0, 50),
      coverage: checklist.length > 0
        ? `${checklist.filter(c => c.status === 'implemented').length}/${checklist.length}`
        : 'no checklist found',
      status: 'assess_complete',
    };
  }

  /** Generate structured report. */
  report(ctx) {
    const { skill, technique, domain, mode, flags } = ctx;
    if (!skill) throw new Error('No SKILL.md found');

    const format = flags.format || 'json';
    const title = skill.frontmatter?.name || technique;

    return {
      action: 'report',
      technique,
      domain,
      mode: mode.toUpperCase(),
      report: {
        title,
        description: skill.frontmatter?.description || '',
        attackTechniques: skill.attackIds,
        tools: skill.tools,
        sections: skill.sections.map(s => s.title),
        tableCount: skill.tables.length,
        codeBlockCount: skill.codeBlocks.length,
        recommendations: this.extractRecommendations(skill),
      },
      format,
      status: 'report_complete',
    };
  }

  /** Extract recommendations from sections named remediation, mitigation, etc. */
  extractRecommendations(skill) {
    const recs = [];
    const recSections = skill.sections.filter(s =>
      /remediat|mitigat|recommend|harden|defense|protect|prevent/i.test(s.title)
    );
    for (const s of recSections) {
      // Extract list items
      const items = s.content.match(/^[-*] .+$/gm);
      if (items) {
        recs.push(...items.map(i => i.replace(/^[-*] /, '').trim()));
      }
    }
    return recs.slice(0, 20);
  }
}
