// @ts-nocheck
/**
 * Design Elevation Extension v2.0.0
 *
 * Auto-detects design/UI/frontend work and injects Rams × Crawford × Pharrell
 * three-layer methodology into agent system prompts.
 *
 * Review fixes: W1 (userMessage), W2 (duplicate guard), W3 (session reset)
 */

import type { ExtensionAPI } from "@helios-agent/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { appendFileSync, mkdirSync } from "node:fs";
// Note: writeFileSync, readFileSync, existsSync imported below with registerExtension
import { join } from "node:path";
import { homedir } from "node:os";
import { registerExtension } from '../lib/load-order.ts';
import { createHash } from "node:crypto";
import { existsSync, writeFileSync, readFileSync } from "node:fs";
import { appendDynamic } from '../../lib/cache-markers.ts';

const HOME = homedir();
const LOG_DIR = join(HOME, ".pi", "agent", "governance");
const EVENTS_FILE = join(LOG_DIR, "events.jsonl");

interface State {
  designDetected: boolean;
  backendDetected: boolean;
  interrogationDone: boolean;
  lastInjectionTurn: number;
  turnCount: number;
}

const INITIAL_STATE: State = {
  designDetected: false,
  backendDetected: false,
  interrogationDone: false,
  lastInjectionTurn: 0,
  turnCount: 0,
};

let state: State = { ...INITIAL_STATE };

// ── 6-Channel Multi-Signal Design Detection ──────────────────────────
// Imported from shared module — single source of truth across all detection sites.
// See: lib/design-detection.ts for the full 6-channel implementation.
import {
  detectDesignWork,
  DESIGN_KEYWORDS_HIGH,
  DESIGN_KEYWORDS_MEDIUM,
  DESIGN_KEYWORDS_LOW,
  DESIGN_DETECTION_THRESHOLD,
  type DesignDetectionResult,
  BACKEND_PATTERN,
} from '../../lib/design-detection.ts';

// Legacy alias for DESIGN_IN_TASK_PATTERN (used in tool_call handler)
const DESIGN_IN_TASK_PATTERN = DESIGN_KEYWORDS_LOW;

const FULL_INJECTION = `<design-elevation>
## Design Elevation Active

Three-layer methodology for this design work:

**STRUCTURE (Rams):** Every element purposeful. Grid consistent. Color functional. Nothing arbitrary. Ask: "Can I still remove something?"

**TEXTURE (Crawford):** Temperature appropriate. Tactile cues present (grain, shadow, depth). Layout breathes. The most-used moment got care. Ask: "How does this feel to inhabit?"

**CHARACTER (Pharrell):** One organic break exists. Something autobiographical to the brand. Tempo matches content. Detail rewards closer inspection. Ask: "Where does this come alive?"

Checklist before shipping:
□ Structure: purposeful, gridded, functional color, nothing arbitrary
□ Texture: warm temperature, tactile layer, breathing room, everyday moments elevated
□ Character: one organic break, brand personality, matched tempo, hidden craft

Emotional checkpoint: For each element, ask "Is it correct?" (Rams) AND "Does it feel cared-for?" (Crawford) AND "Is there a surprise when you look closer?" (Pharrell).

Tempo: storytelling/portfolio → largo (wide spacing, slow reveals). Default → andante. Data/admin → allegro (tight, snappy).

Respect the user's palette and brand. Elevate what they have — don't replace it.

Mandatory Tool Governance:
- When presenting visual design options, UI designs, layout variations, or comparing architectural alternatives to the user, you MUST invoke the design_deck tool rather than printing plain-text option lists.
- When asking questions, confirming scope, soliciting user preferences, or clarifying ambiguous requirements, you MUST invoke the interview tool to present interactive questionnaires rather than asking raw questions in plain chat text.
</design-elevation>`;

const BACKEND_INJECTION = `<design-elevation context="backend">
## Surface Quality Thinking

This data will surface to humans. Shape it for human consumption:
- Pre-compute display-ready fields alongside machine fields (status + status_label)
- Include relative timestamps alongside ISO timestamps
- Use human-readable field names over abbreviations
- Add summary fields for collections ("47 items, 12 need attention")
- Ask: "How will a human encounter this data?"
</design-elevation>`;

function logEvent(type: string, data?: Record<string, unknown>): void {
  try {
    mkdirSync(LOG_DIR, { recursive: true });
    appendFileSync(EVENTS_FILE, JSON.stringify({ ts: new Date().toISOString(), type, ...data }) + "\n");
  } catch (e) { process.stderr.write(`[design-elevation] never crash: ${String(e)}\n`); }
}


// ── Graph Persistence (Design Memory) ─────────────────────────────────

let _graphMod: any = null;
let _graphModResolved = false;

function getGraph(): any {
  if (_graphModResolved) return _graphMod;
  _graphModResolved = true;
  try {
    const mg = require('../../lib/graph/graph-transport.js');
    
    _graphMod = {
      async write(cypher: string, params: Record<string, unknown> = {}) {
        await mg.rawWrite(cypher, params);
      },
      async read(cypher: string, params: Record<string, unknown> = {}) {
        return await mg.rawRead(cypher, params);
      },
      async close() {}
    };
  } catch (e) {
    process.stderr.write(`[design-elevation] graph unavailable (non-fatal): ${String(e)}\n`);
    _graphMod = null;
  }
  return _graphMod;
}

function decisionHash(project: string, decision: string): string {
  return createHash('md5').update(`${project}::${decision.toLowerCase().trim()}`).digest('hex');
}

const DESIGNS_DIR_NAME = '.planning/designs';

function ensureDesignsDir(projectPath: string): string {
  const dir = join(projectPath, DESIGNS_DIR_NAME);
  try { mkdirSync(dir, { recursive: true }); } catch (e) { /* ignore */ }
  return dir;
}

// ── Decision Extraction from Agent Response ───────────────────────────

const DECISION_PATTERNS = [
  /(?:chose|decided|selected|went with|opted for|using|picked)\s+(.{10,120}?)(?:\.|\n|$)/gi,
  /(?:decision|choice):\s*(.{10,120}?)(?:\.|\n|$)/gi,
];

const RATIONALE_PATTERNS = [
  /(?:because|since|rationale|reason|due to|this (?:allows|enables|ensures|provides))\s+(.{10,120}?)(?:\.|\n|$)/gi,
];

const ALTERNATIVE_PATTERNS = [
  /(?:instead of|rather than|over|versus|vs\.?|considered|rejected)\s+(.{10,80}?)(?:\.|\n|,|$)/gi,
];

interface ExtractedDecision {
  decision: string;
  rationale: string | null;
  alternatives: string[];
}

function extractDecisions(responseText: string): ExtractedDecision[] {
  const decisions: ExtractedDecision[] = [];
  const seen = new Set<string>();

  for (const pattern of DECISION_PATTERNS) {
    let match;
    pattern.lastIndex = 0;
    while ((match = pattern.exec(responseText)) !== null) {
      const decision = match[1].trim();
      if (decision.length < 10 || seen.has(decision.toLowerCase())) continue;
      seen.add(decision.toLowerCase());

      // Look for rationale near this decision (within 300 chars)
      const nearbyText = responseText.slice(
        Math.max(0, match.index - 100),
        Math.min(responseText.length, match.index + match[0].length + 300)
      );

      let rationale: string | null = null;
      for (const rp of RATIONALE_PATTERNS) {
        rp.lastIndex = 0;
        const rm = rp.exec(nearbyText);
        if (rm) { rationale = rm[1].trim(); break; }
      }

      const alternatives: string[] = [];
      for (const ap of ALTERNATIVE_PATTERNS) {
        ap.lastIndex = 0;
        let am;
        while ((am = ap.exec(nearbyText)) !== null) {
          const alt = am[1].trim();
          if (alt.length >= 3 && alt.length <= 80) alternatives.push(alt);
        }
      }

      decisions.push({ decision, rationale, alternatives: [...new Set(alternatives)] });
      if (decisions.length >= 5) break; // cap at 5 per response
    }
    if (decisions.length >= 5) break;
  }

  return decisions;
}

// ── Persist Design Decision to Graph ──────────────────────────────────

async function persistDecision(
  project: string,
  decision: string,
  rationale: string | null,
  alternatives: string[],
  createdBy: string,
  sessionId: string,
  context?: string,
  artifactPath?: string,
): Promise<string | null> {
  const graph = getGraph();
  if (!graph) return null;

  const hash = decisionHash(project, decision);
  try {
    await graph.write(`
      MERGE (dd:DesignDecision {decision_hash: $hash})
      ON CREATE SET
        dd.decision = $decision,
        dd.rationale = $rationale,
        dd.context = $context,
        dd.alternatives = $alternatives,
        dd.confidence = 1.0,
        dd.created_by = $createdBy,
        dd.session_id = $sessionId,
        dd.project = $project,
        dd.created_at = datetime()
      ON MATCH SET
        dd.confidence = CASE WHEN dd.confidence < 1.0 THEN dd.confidence + 0.1 ELSE 1.0 END,
        dd.rationale = CASE WHEN $rationale IS NOT NULL THEN $rationale ELSE dd.rationale END
    `, {
      hash,
      decision,
      rationale: rationale || null,
      context: context || null,
      alternatives: JSON.stringify(alternatives),
      createdBy,
      sessionId,
      project,
    });

    // Link to artifact if provided
    if (artifactPath) {
      await graph.write(`
        MATCH (dd:DesignDecision {decision_hash: $hash})
        MERGE (da:DesignArtifact {path: $path, project: $project})
        ON CREATE SET da.type = 'html_mockup', da.created_at = datetime(), da.description = $decision
        MERGE (dd)-[:REFERENCES]->(da)
      `, { hash, path: artifactPath, project, decision });
    }

    logEvent('design_decision_persisted', { project, hash, decision: decision.slice(0, 60) });
    return hash;
  } catch (e) {
    process.stderr.write(`[design-elevation] persist failed (non-fatal): ${String(e)}\n`);
    return null;
  }
}

// ── Persist Design Pattern ────────────────────────────────────────────

async function persistPattern(
  project: string,
  name: string,
  description: string,
  files: string[],
  tokens?: Record<string, string>,
): Promise<void> {
  const graph = getGraph();
  if (!graph) return;

  try {
    await graph.write(`
      MERGE (dp:DesignPattern {name: $name, project: $project})
      ON CREATE SET
        dp.description = $description,
        dp.files = $files,
        dp.tokens = $tokens,
        dp.usage_count = 1
      ON MATCH SET
        dp.usage_count = dp.usage_count + 1,
        dp.description = $description,
        dp.files = $files
    `, {
      name,
      project,
      description,
      files: JSON.stringify(files),
      tokens: tokens ? JSON.stringify(tokens) : null,
    });
  } catch (e) {
    process.stderr.write(`[design-elevation] pattern persist failed (non-fatal): ${String(e)}\n`);
  }
}

// ── StyleGuide Auto-Capture ───────────────────────────────────────────

async function captureStyleGuide(projectPath: string): Promise<void> {
  const graph = getGraph();
  if (!graph) return;

  try {
    // Check if we already have a recent style guide
    const existing = await graph.read(
      'MATCH (sg:StyleGuide {project: $project}) RETURN sg.captured_at AS ts LIMIT toInteger(1)',
      { project: projectPath }
    );
    // Skip if captured in the last 24 hours
    if (existing?.length > 0) return;
  } catch (e) { /* continue — first capture */ }

  const style: Record<string, string> = {};

  // Scan tailwind config
  for (const tw of ['tailwind.config.ts', 'tailwind.config.js', 'tailwind.config.mjs']) {
    const twPath = join(projectPath, tw);
    if (existsSync(twPath)) {
      try {
        const twContent = readFileSync(twPath, 'utf-8');
        // Extract primary color
        const colorMatch = twContent.match(/primary['"\s]*:[\s]*['"](#[0-9a-fA-F]{3,8}|[a-z]+)/);
        if (colorMatch) style.primary_color = colorMatch[1];
        // Extract font family
        const fontMatch = twContent.match(/fontFamily[\s\S]*?sans['"\s]*:[\s]*\[['"]([^'"]+)/);
        if (fontMatch) style.font_family = fontMatch[1];
        style.captured_from = tw;
        style.ui_framework = twContent.includes('react') ? 'React + Tailwind' : 'Tailwind';
      } catch (e) { /* skip */ }
      break;
    }
  }

  // Scan package.json for framework
  const pkgPath = join(projectPath, 'package.json');
  if (!style.ui_framework && existsSync(pkgPath)) {
    try {
      const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
      const deps = { ...pkg.dependencies, ...pkg.devDependencies };
      if (deps['next'] || deps['react']) style.ui_framework = deps['next'] ? 'Next.js + React' : 'React';
      else if (deps['vue']) style.ui_framework = 'Vue';
      else if (deps['svelte'] || deps['@sveltejs/kit']) style.ui_framework = 'Svelte';
      else if (deps['angular']) style.ui_framework = 'Angular';
      if (!style.captured_from) style.captured_from = 'package.json';
    } catch (e) { /* skip */ }
  }

  // Only persist if we found something
  if (Object.keys(style).length <= 1) return; // just captured_from isn't enough

  const graph2 = getGraph();
  if (!graph2) return;
  try {
    await graph2.write(`
      MERGE (sg:StyleGuide {project: $project})
      SET sg.primary_color = $primary_color,
          sg.font_family = $font_family,
          sg.spacing_unit = $spacing_unit,
          sg.border_radius = $border_radius,
          sg.ui_framework = $ui_framework,
          sg.captured_from = $captured_from,
          sg.captured_at = datetime()
    `, {
      project: projectPath,
      primary_color: style.primary_color || null,
      font_family: style.font_family || null,
      spacing_unit: style.spacing_unit || '4px',
      border_radius: style.border_radius || null,
      ui_framework: style.ui_framework || null,
      captured_from: style.captured_from || null,
    });
    logEvent('style_guide_captured', { project: projectPath, framework: style.ui_framework });
  } catch (e) {
    process.stderr.write(`[design-elevation] style guide capture failed (non-fatal): ${String(e)}\n`);
  }
}

function extractUserMessage(event: any): string {
  try {
    const msgs = event?.messages;
    if (Array.isArray(msgs)) {
      const last = [...msgs].reverse().find((m: any) => m.role === 'user');
      if (last?.content) {
        return typeof last.content === 'string' ? last.content : JSON.stringify(last.content);
      }
    }
    if (event?.userMessage) {
      return typeof event.userMessage === 'string' ? event.userMessage : JSON.stringify(event.userMessage);
    }
    return '';
  } catch (e) { process.stderr.write(`[design-elevation] operation failed: ${String(e)}\n`); return ''; }
}

export default function designElevationExtension(pi: ExtensionAPI): void {
  registerExtension('design-elevation');

  // Close driver on session shutdown
  pi.on('session_shutdown', async () => {
    if (_graphMod && typeof _graphMod.close === 'function') {
      try { await _graphMod.close(); } catch {}
    }
  });

  pi.on("session_start", async () => {
    try { state = { ...INITIAL_STATE }; } catch (e) { process.stderr.write(`[design-elevation] never crash: ${String(e)}\n`); }
  });

  pi.on("before_agent_start", async (event: any) => {
    try {
      state.turnCount++;
      const userContent = extractUserMessage(event);
      if (!userContent) return;

      const detection = detectDesignWork(userContent);
      const hasDesign = detection.isDesign;
      const hasBackend = BACKEND_PATTERN.test(userContent);
      (state as any)._lastDetection = detection;

      if (hasDesign && !state.designDetected) {
        state.designDetected = true;
        logEvent('design_elevation_activated', {
          turn: state.turnCount,
          score: detection.score,
          channels: detection.channels,
          confidence: detection.confidence,
        });
      }
      if (hasBackend && !state.backendDetected) {
        state.backendDetected = true;
      }

      const shouldInject = (hasDesign || hasBackend) && (
        state.lastInjectionTurn === 0 ||
        state.turnCount - state.lastInjectionTurn >= 10
      );
      if (!shouldInject) return;

      const alreadyPresent = ((event.systemPrompt ?? '') as string).includes('<design-elevation>');
      if (alreadyPresent) return;

      const injection = hasDesign ? FULL_INJECTION : BACKEND_INJECTION;
      state.lastInjectionTurn = state.turnCount;

      if (!hasDesign && hasBackend) {
        logEvent('design_elevation_backend_nudge', { turn: state.turnCount });
      }

      return { systemPrompt: appendDynamic(event.systemPrompt ?? '', '\n\n' + injection) };
    } catch (e) { process.stderr.write(`[design-elevation] never crash: ${String(e)}\n`); }
  });

  pi.on("tool_call", async (event: any) => {
    try {
      const tool = event?.tool ?? event?.name ?? '';
      if (tool === 'interview') {
        if (!state.interrogationDone) {
          state.interrogationDone = true;
          logEvent('design_elevation_interrogation', { turn: state.turnCount });
        }
        return;
      }
      // Wave 4: Auto-persist design_deck selections as DesignDecision nodes
      if (tool === 'design_deck' && state.designDetected) {
        try {
          const argsStr = JSON.stringify(event?.args ?? event?.arguments ?? '');
          // Extract slide selections from design_deck results
          // The design_deck returns selections with labels
          logEvent('design_deck_selection', { turn: state.turnCount });
          // Persist will happen via tool_result handler when deck completes
          (state as any)._pendingDeckPersist = true;
        } catch (e) {
          process.stderr.write(`[design-elevation] deck persist prep failed (non-fatal): ${String(e)}\n`);
        }
      }
      if (tool === 'subagent' && state.designDetected && !state.interrogationDone) {
        const argsStr = JSON.stringify(event?.args ?? event?.arguments ?? '');
        const taskDetection = detectDesignWork(argsStr);
          if (taskDetection.isDesign) {
          pi.notify("💡 Design elevation — consider asking how this should *feel* before generating.");
        }
      }
    } catch (e) { process.stderr.write(`[design-elevation] never crash: ${String(e)}\n`); }
  });


  // ── Wave 2: Auto-extract design decisions from agent responses ──────
  pi.on("tool_result", async (event: any) => {
    try {
      // Wave 4: Persist design_deck selections as DesignDecision nodes
      if (event?.toolName === 'design_deck' && (state as any)._pendingDeckPersist) {
        try {
          (state as any)._pendingDeckPersist = false;
          const resultContent = event?.content;
          let responseText = '';
          if (typeof resultContent === 'string') responseText = resultContent;
          else if (Array.isArray(resultContent)) {
            responseText = resultContent.filter((p: any) => p.type === 'text').map((p: any) => p.text).join('\n');
          }
          // Extract selections (format: "- slideId: Selected Option Label")
          const selections = responseText.match(/(?:selected|chose|picked).*?[:\-]\s*(.+)/gi);
          if (selections && selections.length > 0) {
            const projectPath = process.cwd();
            const sessionId = `deck-${Date.now()}`;
            for (const sel of selections) {
              const cleaned = sel.replace(/^.*?[:\-]\s*/, '').trim();
              if (cleaned.length > 5) {
                await persistDecision(
                  projectPath,
                  `Design deck selection: ${cleaned}`,
                  'Selected via design_deck visual comparison',
                  [],
                  'orchestrator',
                  sessionId,
                  'design_deck',
                );
              }
            }
            logEvent('design_deck_selections_persisted', {
              count: selections.length,
              project: projectPath,
            });
          }
        } catch (e) {
          process.stderr.write(`[design-elevation] deck selection persist failed (non-fatal): ${String(e)}\n`);
        }
        return;
      }
      if (event?.toolName !== 'subagent') return;
      if (!state.designDetected) return;

      // Extract response text
      const resultContent = event?.content;
      let responseText = '';
      if (typeof resultContent === 'string') {
        responseText = resultContent;
      } else if (Array.isArray(resultContent)) {
        responseText = resultContent
          .filter((p: any) => p.type === 'text')
          .map((p: any) => p.text)
          .join('\n');
      }
      if (responseText.length < 50) return;

      // Extract and persist design decisions
      const projectPath = process.cwd();
      const decisions = extractDecisions(responseText);
      if (decisions.length === 0) return;

      const sessionId = `session-${Date.now()}`;
      for (const d of decisions) {
        await persistDecision(
          projectPath,
          d.decision,
          d.rationale,
          d.alternatives,
          'worker',
          sessionId,
        );
      }

      if (decisions.length > 0) {
        logEvent('design_decisions_auto_extracted', {
          count: decisions.length,
          project: projectPath,
          turn: state.turnCount,
        });
      }
    } catch (e) {
      process.stderr.write(`[design-elevation] auto-extract failed (non-fatal): ${String(e)}\n`);
    }
  });

  // ── Wave 2: StyleGuide auto-capture on first design detection ───────
  pi.on("before_agent_start", async (event: any) => {
    try {
      if (!state.designDetected) return;
      // Only capture once per session
      if ((state as any)._styleGuideCaptured) return;
      (state as any)._styleGuideCaptured = true;
      
      const projectPath = process.cwd();
      // Fire-and-forget — don't block agent start
      captureStyleGuide(projectPath).catch(e => {
        process.stderr.write(`[design-elevation] style capture bg failed (non-fatal): ${String(e)}\n`);
      });
    } catch (e) {
      process.stderr.write(`[design-elevation] never crash: ${String(e)}\n`);
    }
  });


  // ── Wave 2: save_design_decision tool ───────────────────────────────
  pi.registerTool({
    name: "save_design_decision",
    description: "Persist an important UI/design decision to the design memory graph. Workers should call this when making significant design choices (layout, component selection, interaction patterns, visual hierarchy).",
    parameters: Type.Object({
      decision: Type.String({ description: "The design decision made (e.g., 'Use tab navigation for settings page')" }),
      rationale: Type.String({ description: "Why this decision was made" }),
      alternatives: Type.Optional(Type.Array(Type.String(), { description: "Other options that were considered" })),
      context: Type.Optional(Type.String({ description: "Feature/area this decision applies to (e.g., 'settings-page')" })),
      files: Type.Optional(Type.Array(Type.String(), { description: "Files affected by this decision" })),
      artifact_path: Type.Optional(Type.String({ description: "Path to a design artifact (mockup, screenshot) related to this decision" })),
      pattern_name: Type.Optional(Type.String({ description: "If this decision establishes a reusable pattern, name it (e.g., 'card-grid-layout')" })),
      pattern_description: Type.Optional(Type.String({ description: "Description of the reusable pattern" })),
    }),
    async execute(_toolCallId: string, args: any, _signal: any, _onUpdate: any, _ctx: any) {
      try {
        const projectPath = process.cwd();
        const sessionId = `session-${Date.now()}`;

        const hash = await persistDecision(
          projectPath,
          args.decision,
          args.rationale,
          args.alternatives || [],
          'worker',
          sessionId,
          args.context,
          args.artifact_path,
        );

        // If a pattern was defined, persist it too
        if (args.pattern_name) {
          await persistPattern(
            projectPath,
            args.pattern_name,
            args.pattern_description || args.decision,
            args.files || [],
          );
        }

        const msg = `Design decision saved: "${args.decision.slice(0, 60)}..."`;
        return {
          content: [{ type: 'text' as const, text: msg }],
          details: { status: 'persisted', decision_hash: hash, project: projectPath },
        };
      } catch (e) {
        return {
          content: [{ type: 'text' as const, text: `Design decision error: ${String(e)}` }],
          details: { status: 'error', message: String(e) },
        };
      }
    },
  });

  pi.registerTool({
    name: "design_elevation_status",
    description: "Check current design elevation detection state",
    parameters: Type.Object({}),
    async execute(_toolCallId: string, _params: any, _signal: any, _onUpdate: any, _ctx: any) {
      try {
        const lastDetection = (state as any)._lastDetection || null;
        const details = { ...state, lastDetection };
        return {
          content: [{ type: 'text' as const, text: JSON.stringify(details, null, 2) }],
          details,
        };
      } catch (e) {
        return {
          content: [{ type: 'text' as const, text: `Design elevation status error: ${String(e)}` }],
        };
      }
    },
  });

  pi.registerCommand?.("elevation-status", {
    description: "Show design elevation status",
    handler: async () => {
      try {
        const parts: string[] = [];
        parts.push(state.designDetected ? "🎨 Design work detected" : "⚪ No design work detected");
        if (state.backendDetected) parts.push("📡 Backend surface quality active");
        if (state.interrogationDone) parts.push("✅ Crawford interrogation done");
        parts.push(`Turn: ${state.turnCount}`);
        pi.notify(parts.join(" · "));
      } catch (e) { process.stderr.write(`[design-elevation] never crash: ${String(e)}\n`); }
    },
  });
}