/**
 * Symbol Creator HTTP API Service
 *
 * Provides a structured API for creating KiCad/Fusion 360 schematic symbols.
 * Handles .kicad_sym generation, SVG export, branded viewer HTML, lint, validate,
 * and delivery to desktop EDA tools.
 *
 * Port: 8781
 *
 * All endpoints accept POST with JSON body.
 */

import { createServer } from 'http';
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, readFile as readFileCb } from 'fs';
import { readFile } from 'fs/promises';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { exec } from 'child_process';

const __dirname = dirname(fileURLToPath(import.meta.url));
const SAMPLES_DIR = join(__dirname, 'samples');

import { generateICSym, generateDiscreteSym, warmBaselineCache } from './lib/sym-gen.js';
import { generateBrandedViewer } from './lib/viewer-gen.js';
import { generateMetadata } from './lib/metadata-gen.js';
import { generateReadme } from './lib/readme-gen.js';
import { generateLbr } from './lib/lbr-gen.js';
import { symExportSvg, symSearch } from '../viewer/kicad-api-client.js';
import { lintKicadSymbol } from '../viewer/kicad-lint.js';
import { validateSymbolFolder } from '../viewer/kicad-validate.js';

const PORT = parseInt(process.env.PORT || '8781', 10);
const SYMBOLS_DIR = '/home/adom/project/project-content/schematics/symbols';

// In-memory cache of SymView HTML keyed by symbol name, served via GET /viewer/:name
const viewerCache = new Map();

// Build the public URL for this service using the VS Code proxy pattern
const VSCODE_PROXY_URI = process.env.VSCODE_PROXY_URI || '';

// ── Helpers ──────────────────────────────────────────────────────────

function parseBody(req) {
  return new Promise((resolve, reject) => {
    let data = '';
    req.on('data', chunk => data += chunk);
    req.on('end', () => {
      if (!data) return resolve({});
      try { resolve(JSON.parse(data)); }
      catch (e) { reject(new Error('Invalid JSON')); }
    });
    req.on('error', reject);
  });
}

function json(res, status, data) {
  res.writeHead(status, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify(data));
}

async function pushToViewer(html, name) {
  // Cache the HTML so the GET /viewer/:name route can serve it
  viewerCache.set(name, html);

  // Also write it to disk at the canonical location
  const folder = join(SYMBOLS_DIR, name);
  mkdirSync(folder, { recursive: true });
  const htmlPath = join(folder, `${name}-viewer.html`);
  writeFileSync(htmlPath, html);

  // Build the proxy URL and open / refresh a Hydrogen webview tab
  if (VSCODE_PROXY_URI) {
    const base = VSCODE_PROXY_URI.replace('{{port}}', String(PORT)).replace(/\/$/, '');
    const proxyUrl = `${base}/viewer/${encodeURIComponent(name)}`;
    const cmd = `adom-cli hydrogen webview open-or-refresh --name "adom-symbol: ${name}" --url "${proxyUrl}"`;
    await new Promise((resolve) => {
      exec(cmd, (err) => {
        if (err) console.error(`[symbol-creator] hydrogen webview open-or-refresh failed: ${err.message}`);
        resolve();
      });
    });
  } else {
    console.warn('[symbol-creator] VSCODE_PROXY_URI not set — skipping Hydrogen webview open');
  }
}

function symbolDir(symbolName) {
  return join(SYMBOLS_DIR, symbolName);
}

// ── Route handlers ──────────────────────────────────────────────────

/**
 * POST /sym/create
 *
 * Create a complete symbol package from structured data.
 */
async function handleCreate(body) {
  const {
    symbolName,
    manufacturer = '',
    package: pkg = '',
    description = '',
    datasheetUrl = '',
    category = 'ic',       // 'ic' or 'discrete'
    pins = [],
    groups = [],
    discreteBaseline,      // { library, symbol } for discrete
    referencePrefix,
    pinOverrides = [],
    preferences = {},
  } = body;

  if (!symbolName) throw new Error('symbolName is required');
  if (pins.length === 0) throw new Error('pins array is required');

  const folder = symbolDir(symbolName);
  mkdirSync(folder, { recursive: true });

  const files = [];

  // 1. Generate .kicad_sym
  let symContent;
  if (category === 'discrete' && discreteBaseline) {
    symContent = await generateDiscreteSym({
      symbolName, manufacturer, package: pkg, description, datasheetUrl,
      referencePrefix, discreteBaseline, pinOverrides,
    });
  } else {
    // Determine reference prefix
    const refPrefix = referencePrefix || (category === 'discrete' ? 'D' : 'U');
    symContent = generateICSym({
      symbolName, manufacturer, package: pkg, description, datasheetUrl,
      referencePrefix: refPrefix, pins, groups, preferences,
    });
  }

  const symPath = join(folder, `${symbolName}.kicad_sym`);
  writeFileSync(symPath, symContent);
  files.push(symPath);

  // 2. Generate metadata JSON
  const metadata = generateMetadata({
    symbolName, manufacturer, package: pkg, description, datasheetUrl, pins, groups,
  });
  const metaPath = join(folder, `${symbolName}-metadata.json`);
  writeFileSync(metaPath, JSON.stringify(metadata, null, 2) + '\n');
  files.push(metaPath);

  // 3. Generate README
  const readme = generateReadme({
    symbolName, manufacturer, package: pkg, description, datasheetUrl, pins, groups,
  });
  const readmePath = join(folder, 'README.md');
  writeFileSync(readmePath, readme);
  files.push(readmePath);

  // 4. Export SVG via KiCad service
  let svgContent;
  try {
    svgContent = await symExportSvg(symPath, symbolName);
    const svgPath = join(folder, `${symbolName}.svg`);
    writeFileSync(svgPath, svgContent);
    files.push(svgPath);
  } catch (err) {
    // SVG export failed — continue without it
    console.error(`[symbol-creator] SVG export failed: ${err.message}`);
  }

  // 5. Generate branded viewer HTML
  let viewerHtml;
  try {
    viewerHtml = await generateBrandedViewer(symPath, symbolName, {
      pinDescriptions: metadata.pinDescriptions,
      groupDescriptions: metadata.groupDescriptions,
      datasheetUrl,
      manufacturer,
    });
    const viewerPath = join(folder, `${symbolName}-viewer.html`);
    writeFileSync(viewerPath, viewerHtml);
    files.push(viewerPath);
  } catch (err) {
    console.error(`[symbol-creator] Viewer generation failed: ${err.message}`);
  }

  // 6. Run lint
  let lintResult = { passed: true, errors: [], warnings: [] };
  try {
    lintResult = await lintKicadSymbol(symPath, symbolName);
  } catch (err) {
    lintResult = { passed: false, errors: [{ check: 'lint-error', message: err.message }], warnings: [] };
  }

  // 7. Run validate
  let validateResult = { passed: true, errors: [], warnings: [] };
  try {
    validateResult = await validateSymbolFolder(folder, symbolName);
  } catch (err) {
    validateResult = { passed: false, errors: [{ check: 'validate-error', message: err.message }], warnings: [] };
  }

  // 8. Open in Hydrogen webview
  if (viewerHtml) {
    await pushToViewer(viewerHtml, symbolName);
  }

  return {
    folder,
    files,
    lint: lintResult,
    validate: validateResult,
  };
}

/**
 * POST /sym/preview
 *
 * (Re-)preview an existing symbol in a Hydrogen webview tab.
 */
async function handlePreview(body) {
  const { symbolName, folderPath } = body;
  const folder = folderPath || symbolDir(symbolName);
  if (!symbolName) throw new Error('symbolName is required');

  const symPath = join(folder, `${symbolName}.kicad_sym`);
  if (!existsSync(symPath)) throw new Error(`Symbol file not found: ${symPath}`);

  // Load metadata
  const metaPath = join(folder, `${symbolName}-metadata.json`);
  let pinDescriptions = {};
  let groupDescriptions = {};
  let datasheetUrl = '';
  let manufacturer = '';
  if (existsSync(metaPath)) {
    const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
    pinDescriptions = meta.pinDescriptions || {};
    groupDescriptions = meta.groupDescriptions || {};
    datasheetUrl = meta.datasheetUrl || '';
    manufacturer = meta.manufacturer || '';
  }

  // Generate viewer HTML
  const viewerHtml = await generateBrandedViewer(symPath, symbolName, {
    pinDescriptions,
    groupDescriptions,
    datasheetUrl,
    manufacturer,
  });

  // Save viewer HTML
  const viewerPath = join(folder, `${symbolName}-viewer.html`);
  writeFileSync(viewerPath, viewerHtml);

  // Push to viewer
  await pushToViewer(viewerHtml, symbolName);

  return { folder, viewerPath, sent: true };
}

/**
 * POST /sym/lint
 */
async function handleLint(body) {
  const { symbolName, folderPath } = body;
  const folder = folderPath || symbolDir(symbolName);
  if (!symbolName) throw new Error('symbolName is required');

  const symPath = join(folder, `${symbolName}.kicad_sym`);
  return lintKicadSymbol(symPath, symbolName);
}

/**
 * POST /sym/validate
 */
async function handleValidate(body) {
  const { symbolName, folderPath } = body;
  const folder = folderPath || symbolDir(symbolName);
  if (!symbolName) throw new Error('symbolName is required');

  return validateSymbolFolder(folder, symbolName);
}

/**
 * POST /sym/search
 */
async function handleSearch(body) {
  const { query, limit = 20 } = body;
  if (!query) throw new Error('query is required');

  return symSearch(query, limit);
}

/**
 * POST /sym/deliver
 */
async function handleDeliver(body) {
  const { symbolName, target = 'kicad', folderPath } = body;
  const folder = folderPath || symbolDir(symbolName);
  if (!symbolName) throw new Error('symbolName is required');

  const symPath = join(folder, `${symbolName}.kicad_sym`);
  if (!existsSync(symPath)) throw new Error(`Symbol file not found: ${symPath}`);

  // Final lint + validate
  const lintResult = await lintKicadSymbol(symPath, symbolName);
  const validateResult = await validateSymbolFolder(folder, symbolName);

  if (!lintResult.passed || !validateResult.passed) {
    return {
      success: false,
      reason: 'lint or validation failed',
      lint: lintResult,
      validate: validateResult,
    };
  }

  if (target === 'fusion360') {
    // Generate .lbr
    const content = readFileSync(symPath, 'utf-8');
    const metaPath = join(folder, `${symbolName}-metadata.json`);
    const meta = existsSync(metaPath) ? JSON.parse(readFileSync(metaPath, 'utf-8')) : {};

    // Parse pins from the .kicad_sym for LBR conversion
    const pinRegex = /\(pin\s+(\w+)\s+\w+\s+\(at\s+([-\d.]+)\s+([-\d.]+)\s+(\d+)\)\s+\(length\s+([-\d.]+)\)\s+\(name\s+"([^"]*)"[^)]*\)\s+\(number\s+"([^"]*)"[^)]*\)\)/g;
    const lbrPins = [];
    let m;
    while ((m = pinRegex.exec(content)) !== null) {
      lbrPins.push({
        type: m[1], x: parseFloat(m[2]), y: parseFloat(m[3]),
        angle: parseInt(m[4]), length: parseFloat(m[5]),
        name: m[6], number: m[7],
      });
    }

    // Parse body rectangle
    const rectMatch = content.match(/\(rectangle\s+\(start\s+([-\d.]+)\s+([-\d.]+)\)\s+\(end\s+([-\d.]+)\s+([-\d.]+)\)/);
    const body = rectMatch ? {
      left: parseFloat(rectMatch[1]), top: parseFloat(rectMatch[2]),
      right: parseFloat(rectMatch[3]), bottom: parseFloat(rectMatch[4]),
    } : null;

    const lbrContent = generateLbr({
      symbolName,
      description: meta.description || '',
      referencePrefix: 'U',
      pins: lbrPins,
      body,
    });

    const lbrPath = join(folder, `${symbolName}.lbr`);
    writeFileSync(lbrPath, lbrContent);

    return {
      success: true,
      target: 'fusion360',
      lbrPath,
      lint: lintResult,
      validate: validateResult,
    };
  }

  // KiCad delivery — return the file path for Conduit
  return {
    success: true,
    target: 'kicad',
    symPath,
    lint: lintResult,
    validate: validateResult,
  };
}

/**
 * GET /sym/samples
 *
 * List available sample symbols bundled with the service.
 */
function handleSamplesList() {
  if (!existsSync(SAMPLES_DIR)) return [];
  const entries = readdirSync(SAMPLES_DIR, { withFileTypes: true });
  return entries
    .filter(e => e.isDirectory() && existsSync(join(SAMPLES_DIR, e.name, `${e.name}.kicad_sym`)))
    .map(e => {
      const metaPath = join(SAMPLES_DIR, e.name, `${e.name}-metadata.json`);
      let description = '';
      let manufacturer = '';
      if (existsSync(metaPath)) {
        const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
        description = meta.description || '';
        manufacturer = meta.manufacturer || '';
      }
      return { name: e.name, description, manufacturer };
    });
}

/**
 * POST /sym/sample
 *
 * Preview a sample symbol. Generates the viewer HTML from the bundled
 * sample artifacts and opens it in a Hydrogen webview tab.
 */
async function handleSample(body) {
  const { symbolName } = body;
  if (!symbolName) throw new Error('symbolName is required');

  const sampleFolder = join(SAMPLES_DIR, symbolName);
  const symPath = join(sampleFolder, `${symbolName}.kicad_sym`);
  if (!existsSync(symPath)) {
    const available = handleSamplesList().map(s => s.name);
    throw new Error(`Sample "${symbolName}" not found. Available: ${available.join(', ')}`);
  }

  // Load metadata
  const metaPath = join(sampleFolder, `${symbolName}-metadata.json`);
  let pinDescriptions = {};
  let groupDescriptions = {};
  let datasheetUrl = '';
  let manufacturer = '';
  if (existsSync(metaPath)) {
    const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
    pinDescriptions = meta.pinDescriptions || {};
    groupDescriptions = meta.groupDescriptions || {};
    datasheetUrl = meta.datasheetUrl || '';
    manufacturer = meta.manufacturer || '';
  }

  // Generate viewer HTML from sample files
  const viewerHtml = await generateBrandedViewer(symPath, symbolName, {
    pinDescriptions,
    groupDescriptions,
    datasheetUrl,
    manufacturer,
  });

  // Push to viewer
  await pushToViewer(viewerHtml, `${symbolName} (sample)`);

  return { symbolName, folder: sampleFolder, sent: true };
}

// ── HTTP Server ──────────────────────────────────────────────────────

const routes = {
  '/sym/create': handleCreate,
  '/sym/preview': handlePreview,
  '/sym/lint': handleLint,
  '/sym/validate': handleValidate,
  '/sym/search': handleSearch,
  '/sym/deliver': handleDeliver,
  '/sym/sample': handleSample,
};

const server = createServer(async (req, res) => {
  // CORS
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

  if (req.method === 'OPTIONS') {
    res.writeHead(204);
    res.end();
    return;
  }

  // Health check
  if (req.url === '/health' || req.url === '/') {
    return json(res, 200, {
      ok: true,
      service: 'symbol-creator',
      version: '0.2.0',
      uptime: Math.floor(process.uptime()),
    });
  }

  // GET /sym/samples — list available sample symbols
  if (req.url === '/sym/samples' && req.method === 'GET') {
    return json(res, 200, handleSamplesList());
  }

  // GET /viewer/:name — serve cached SymView HTML for a symbol
  const viewerMatch = req.url.match(/^\/viewer\/([^/?]+)(\?.*)?$/);
  if (viewerMatch && req.method === 'GET') {
    const name = decodeURIComponent(viewerMatch[1]);
    const cached = viewerCache.get(name);
    if (cached) {
      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
      res.end(cached);
      return;
    }
    // Fall back to reading from disk if not in memory cache
    const htmlPath = join(SYMBOLS_DIR, name, `${name}-viewer.html`);
    if (existsSync(htmlPath)) {
      const html = readFileSync(htmlPath, 'utf-8');
      viewerCache.set(name, html);
      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
      res.end(html);
      return;
    }
    return json(res, 404, { error: `No viewer found for symbol: ${name}` });
  }

  // Route dispatch
  const handler = routes[req.url];
  if (!handler) {
    return json(res, 404, { error: `Unknown route: ${req.url}` });
  }

  if (req.method !== 'POST') {
    return json(res, 405, { error: 'POST required' });
  }

  try {
    const body = await parseBody(req);
    const result = await handler(body);
    json(res, 200, result);
  } catch (err) {
    console.error(`[symbol-creator] ${req.url} error:`, err);
    json(res, 500, { error: err.message });
  }
});

server.listen(PORT, '0.0.0.0', () => {
  console.log(`[symbol-creator] HTTP API listening on 0.0.0.0:${PORT}`);
  console.log(`[symbol-creator] Symbols directory: ${SYMBOLS_DIR}`);
  // Pre-warm baseline cache in background (non-blocking)
  warmBaselineCache().then(stats => {
    console.log(`[symbol-creator] Baseline cache: ${stats.cached} cached, ${stats.fetched} fetched, ${stats.failed} failed (${stats.total} total)`);
  }).catch(err => {
    console.error(`[symbol-creator] Cache warm failed: ${err.message}`);
  });
});

process.on('SIGINT', () => {
  console.log('[symbol-creator] Shutting down...');
  server.close();
  process.exit(0);
});