// @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.
</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`); }
    },
  });
}