/**
 * Blueprint Context Engine – uses PRD/ServiceMap/UIMap as architectural brain
 *
 * When the user edits a VL file, this module finds the relevant blueprint
 * entries and injects them into context. This gives the LLM:
 *   - Full understanding of what a section should do (from UIMap)
 *   - What services are available and their contracts (from ServiceMap)
 *   - The data model and wiring plan (from PRD)
 *   - ValueDomain consistency checking
 *
 * This is impossible in generic codebases – VL's 3-file blueprint system
 * gives VL-Code semantic understanding that Claude Code cannot have.
 */
import fs from 'fs/promises';
import path from 'path';

export class BlueprintContext {
  constructor(projectContext) {
    this.ctx = projectContext;
    this.prd = null;
    this.serviceMap = null;
    this.uiMap = null;
    this.projectMeta = null; // ProjectMeta/1.0 (priority source)
    this.loaded = false;
  }

  /** Load blueprints: try ProjectMeta/1.0 first, then fall back to 3-file system */
  async load() {
    // Priority 1: ProjectMeta/1.0 (unified metadata)
    const meta = this.ctx.getProjectMeta();
    if (meta) {
      this.loadFromProjectMeta(meta);
    }

    // Priority 2: Legacy 3-file system (PRD/ServiceMap/UIMap)
    if (!this.loaded) {
      await this.loadLegacyBlueprints();
    }
  }

  /** Load from ProjectMeta/1.0 — the unified architecture source */
  loadFromProjectMeta(meta) {
    this.projectMeta = meta;

    // Map ProjectMeta to PRD-like structure for backward compatibility
    this.prd = {
      projectName: meta.projectName,
      description: meta.projectDescription,
      vlVersion: meta.vlVersion,
      config: meta.config,
      valueDomains: meta.valueDomains,
      dataModel: {
        entities: (meta.dataSchema?.tables || []).map(t => ({
          name: t.id,
          entityId: t.id,
          fields: t.fields,
          indexes: t.indexes,
        })),
        relations: meta.dataSchema?.relations || [],
      },
    };

    // Map to ServiceMap-like structure
    this.serviceMap = {
      valueDomains: meta.valueDomains,
      serviceDomains: (meta.services || []).map(svc => ({
        domainId: svc.domainId,
        domainName: svc.domainId,
        name: svc.domainId,
        filePath: svc.filePath,
        virtualTables: svc.virtualTables,
        services: (svc.methods || []).map(m => ({
          name: m.id || m.name,
          serviceId: m.serviceId || (svc.domainId && (m.id || m.name) ? `${svc.domainId}.${m.id || m.name}` : (m.id || m.name)),
          sig: m.sig,
          out: m.out || m.returns,
        })),
      })),
    };

    // Map to UIMap-like structure
    this.uiMap = {
      valueDomains: meta.valueDomains,
      components: (meta.components || []).map(cp => ({
        componentId: cp.id,
        componentName: cp.id,
        name: cp.id,
        filePath: cp.filePath,
        description: cp.description,
        props: cp.props,
        events: cp.events,
      })),
      sections: (meta.sections || []).map(sec => ({
        sectionId: sec.id,
        name: sec.id,
        componentName: sec.id,
        filePath: sec.filePath,
        type: sec.type,
        description: sec.description,
        props: sec.props,
        events: sec.events,
        methods: sec.methods,
        keyStates: Object.entries(sec.keyStates || {}).map(([name, type]) => ({
          name, type, sourceEntity: null,
        })),
        servicesUsed: (sec.consumesServices || []).map((svcRef) => ({
          serviceId: typeof svcRef === 'string' ? svcRef : svcRef?.serviceId || svcRef?.id || '',
        })),
        usesComponents: (sec.usesComponents || []).map((compRef) =>
          typeof compRef === 'string' ? compRef : compRef?.componentId || compRef?.id || ''
        ).filter(Boolean),
      })),
      apps: (meta.apps || []).map(app => ({
        appId: app.id,
        appName: app.id,
        name: app.id,
        filePath: app.filePath,
        device: app.device,
        resolution: app.resolution,
        description: app.description,
        pages: app.pages,
        globalVars: app.globalVars,
        wiring: app.wiring,
      })),
    };

    this.loaded = true;
  }

  /** Load legacy 3-file blueprint system (PRD/ServiceMap/UIMap) */
  async loadLegacyBlueprints() {
    const artifactsDir = path.join(this.ctx.workDir, 'Process', 'Artifacts');
    try {
      const prdContent = await fs.readFile(path.join(artifactsDir, 'PRD.json'), 'utf-8');
      this.prd = JSON.parse(prdContent);
    } catch { /* PRD not found */ }

    try {
      const smContent = await fs.readFile(path.join(artifactsDir, 'ServiceMap.json'), 'utf-8');
      this.serviceMap = JSON.parse(smContent);
    } catch { /* ServiceMap not found */ }

    try {
      const uiContent = await fs.readFile(path.join(artifactsDir, 'UIMap.json'), 'utf-8');
      this.uiMap = JSON.parse(uiContent);
    } catch { /* UIMap not found */ }

    this.loaded = !!(this.prd || this.serviceMap || this.uiMap);
  }

  /** Check if blueprints are available */
  hasBlueprints() {
    return this.loaded;
  }

  /** Check if using ProjectMeta/1.0 (vs legacy 3-file) */
  hasProjectMeta() {
    return !!this.projectMeta;
  }

  /**
   * Get relevant blueprint context for a specific file being edited
   * Returns a structured context block the LLM can use for architectural awareness
   */
  getContextForFile(filePath) {
    if (!this.loaded) return null;

    const file = this.ctx.getFile(filePath);
    if (!file) return null;

    const sections = [];

    switch (file.type) {
      case 'section': {
        // Find this section's UIMap entry
        const uiSection = this.findUIMapSection(file.name);
        if (uiSection) {
          sections.push(`## UIMap Blueprint for Section "${file.name}":\n${JSON.stringify(uiSection, null, 2)}`);
        }
        // Find services this section uses
        const services = this.findServicesForSection(file.name);
        if (services.length > 0) {
          sections.push(`## Available Services:\n${JSON.stringify(services, null, 2)}`);
        }
        // Relevant data model entities
        const entities = this.findEntitiesForSection(file.name);
        if (entities.length > 0) {
          sections.push(`## Related Data Entities:\n${JSON.stringify(entities, null, 2)}`);
        }
        break;
      }

      case 'component': {
        // Find component in UIMap
        const uiComponent = this.findUIMapComponent(file.name);
        if (uiComponent) {
          sections.push(`## UIMap Blueprint for Component "${file.name}":\n${JSON.stringify(uiComponent, null, 2)}`);
        }
        break;
      }

      case 'service': {
        // Find service domain in ServiceMap
        const domain = this.findServiceDomain(file.name);
        if (domain) {
          sections.push(`## ServiceMap Blueprint for "${file.name}":\n${JSON.stringify(domain, null, 2)}`);
        }
        // Related database tables
        const tables = this.findTablesForService(file.name);
        if (tables.length > 0) {
          sections.push(`## Related Database Tables:\n${JSON.stringify(tables, null, 2)}`);
        }
        break;
      }

      case 'app': {
        // Find app in UIMap
        const uiApp = this.findUIMapApp(file.name);
        if (uiApp) {
          sections.push(`## UIMap Blueprint for App "${file.name}":\n${JSON.stringify(uiApp, null, 2)}`);
        }
        // Wiring plan
        const wiring = this.findWiringForApp(file.name);
        if (wiring.length > 0) {
          sections.push(`## Wiring Plan:\n${JSON.stringify(wiring, null, 2)}`);
        }
        break;
      }

      case 'database': {
        // Full data model from PRD
        if (this.prd?.dataModel) {
          sections.push(`## Data Model:\n${JSON.stringify(this.prd.dataModel, null, 2)}`);
        }
        break;
      }
    }

    // Always include valueDomains if available
    const vd = this.getValueDomains();
    if (vd) {
      sections.push(`## ValueDomains (enums & constants):\n${JSON.stringify(vd, null, 2)}`);
    }

    if (sections.length === 0) return null;

    return `<system-reminder>\nBlueprint Context for ${file.path}:\n${sections.join('\n\n')}\n</system-reminder>`;
  }

  /**
   * Validate valueDomain consistency across all three files
   * Returns list of inconsistencies found
   */
  validateValueDomains() {
    const issues = [];
    const prdVD = this.prd?.valueDomains;
    const smVD = this.serviceMap?.valueDomains;
    const uiVD = this.uiMap?.valueDomains;

    if (!prdVD) return issues;

    // Check ServiceMap has same valueDomains
    if (smVD) {
      const diff = this.diffValueDomains(prdVD, smVD);
      for (const d of diff) {
        issues.push({ type: 'mismatch', files: ['PRD', 'ServiceMap'], detail: d });
      }
    }

    // Check UIMap has same valueDomains
    if (uiVD) {
      const diff = this.diffValueDomains(prdVD, uiVD);
      for (const d of diff) {
        issues.push({ type: 'mismatch', files: ['PRD', 'UIMap'], detail: d });
      }
    }

    return issues;
  }

  // ========== Lookup helpers ==========

  findUIMapSection(name) {
    return this.uiMap?.sections?.find(s =>
      s.sectionId === name || s.name === name || s.componentName === name
    ) || null;
  }

  findUIMapComponent(name) {
    return this.uiMap?.components?.find(c =>
      c.componentId === name || c.name === name || c.componentName === name
    ) || null;
  }

  findUIMapApp(name) {
    return this.uiMap?.apps?.find(a =>
      a.appId === name || a.name === name || a.appName === name
    ) || null;
  }

  findServiceDomain(name) {
    return this.serviceMap?.serviceDomains?.find(d =>
      d.domainId === name || d.name === name || d.domainName === name
    ) || null;
  }

  findServicesForSection(sectionName) {
    const uiSection = this.findUIMapSection(sectionName);
    if (!uiSection?.servicesUsed) return [];

    return uiSection.servicesUsed.map(su => {
      const [domainName, serviceName] = (su.serviceId || '').split('.');
      const domain = this.findServiceDomain(domainName);
      const service = domain?.services?.find(s => s.name === serviceName || s.serviceId === serviceName);
      return { serviceId: su.serviceId, inputMapping: su.inputMapping, outputMapping: su.outputMapping, contract: service || null };
    }).filter(Boolean);
  }

  findEntitiesForSection(sectionName) {
    if (!this.prd?.dataModel?.entities) return [];
    const uiSection = this.findUIMapSection(sectionName);
    if (!uiSection) return this.prd.dataModel.entities.slice(0, 3);

    // Find entities referenced in keyStates or servicesUsed
    const entityNames = new Set();
    for (const state of (uiSection.keyStates || [])) {
      if (state.sourceEntity) entityNames.add(state.sourceEntity);
    }
    if (entityNames.size === 0) return this.prd.dataModel.entities.slice(0, 3);

    return this.prd.dataModel.entities.filter(e => entityNames.has(e.name) || entityNames.has(e.entityId));
  }

  findTablesForService(serviceName) {
    const domain = this.findServiceDomain(serviceName);
    if (!domain?.virtualTables) return [];
    return domain.virtualTables;
  }

  findWiringForApp(appName) {
    if (!this.prd?.wiringPlan) return [];
    return this.prd.wiringPlan.filter(w =>
      w.source?.appId === appName || w.targets?.some(t => t.appId === appName)
    );
  }

  getValueDomains() {
    return this.prd?.valueDomains || this.serviceMap?.valueDomains || this.uiMap?.valueDomains || null;
  }

  diffValueDomains(vd1, vd2) {
    const diffs = [];
    const enums1 = new Map((vd1.enums || []).map(e => [e.name, e]));
    const enums2 = new Map((vd2.enums || []).map(e => [e.name, e]));

    for (const [name, e1] of enums1) {
      const e2 = enums2.get(name);
      if (!e2) {
        diffs.push(`Enum "${name}" missing in second file`);
      } else if (JSON.stringify(e1.values) !== JSON.stringify(e2.values)) {
        diffs.push(`Enum "${name}" values differ: ${JSON.stringify(e1.values)} vs ${JSON.stringify(e2.values)}`);
      }
    }
    for (const name of enums2.keys()) {
      if (!enums1.has(name)) diffs.push(`Enum "${name}" extra in second file`);
    }
    return diffs;
  }
}
