/**
 * VL Code Generation Pipeline – multi-agent orchestration
 *
 * Implements the 8-agent pipeline from VL_FullStack_CodeGen:
 *   Agent-100: User Req → PRD.json
 *   Agent-200: PRD → Database (.vdb)
 *   Agent-300: PRD + DB → ServiceMap.json
 *   Agent-400: PRD + ServiceMap → UIMap.json
 *   Agent-500: UIMap.components[] → .cp files (parallel)
 *   Agent-600: ServiceMap.serviceDomains[] → .vs files (parallel)
 *   Agent-700: UIMap.sections[] → .sc files (parallel)
 *   Agent-800: UIMap.apps[] → .vx files (parallel)
 *
 * Each stage uses Claude as the LLM with VL-specific system prompts.
 * Stages 5-8 run in parallel per item within each stage.
 */
import { createProvider } from '../core/llm-provider.js';
import { VL_VERSION, VL_VERSION_HEADER } from '../data/versions.js';
import fs from 'fs/promises';
import path from 'path';

const STAGES = [
  { id: 'prd',         agent: 'Agent-100', title: 'PRD Analysis',          output: 'Process/Artifacts/PRD.json' },
  { id: 'database',    agent: 'Agent-200', title: 'Database Generation',   output: 'Database/{project}.vdb' },
  { id: 'serviceMap',  agent: 'Agent-300', title: 'Service Map',           output: 'Process/Artifacts/ServiceMap.json' },
  { id: 'uiMap',       agent: 'Agent-400', title: 'UI Map',               output: 'Process/Artifacts/UIMap.json' },
  { id: 'components',  agent: 'Agent-500', title: 'Components (.cp)',      output: 'ExtComponents/{name}.cp', parallel: true },
  { id: 'services',    agent: 'Agent-600', title: 'Services (.vs)',        output: 'Services/{name}.vs', parallel: true },
  { id: 'sections',    agent: 'Agent-700', title: 'Sections (.sc)',        output: 'Sections/{name}.sc', parallel: true },
  { id: 'apps',        agent: 'Agent-800', title: 'Apps (.vx)',            output: 'Apps/{name}.vx', parallel: true },
];

export class GenerationPipeline {
  constructor(config) {
    this.config = config;
    this.client = createProvider(config);
    this.vars = {};  // Pipeline variables: $prdJson, $vdbContent, etc.
  }

  /**
   * Run the full generation pipeline
   * @param {Object} params - { userRequest, targetLang, mode }
   * @param {Object} callbacks - { onStepStart, onStepDone, onFileWritten, onToken, onDone, onError }
   */
  async run(params, callbacks = {}) {
    const { userRequest, targetLang = 'en', mode = 'create', inlineTheme = false } = params;
    this.config.inlineTheme = inlineTheme;
    const cb = callbacks;
    const filesWritten = [];

    try {
      // Stage 1: PRD
      cb.onStepStart?.({ id: 'prd', title: 'Generating PRD...' });
      const prdResult = await this.callLLM(
        this.buildPRDPrompt(userRequest, targetLang),
        { json: true, stream: true, onToken: cb.onToken }
      );
      this.vars.$prdJson = JSON.parse(prdResult);
      this.vars.$prdJsonStr = prdResult;
      const prdPath = await this.writeArtifact('Process/Artifacts/PRD.json', prdResult);
      filesWritten.push(prdPath);
      cb.onStepDone?.({ id: 'prd', title: 'PRD complete' });
      cb.onFileWritten?.(prdPath);

      // Stage 2: Database
      cb.onStepStart?.({ id: 'database', title: 'Generating database schema...' });
      const dbResult = await this.callLLM(
        this.buildDatabasePrompt(),
        { stream: true, onToken: cb.onToken }
      );
      this.vars.$vdbContent = dbResult;
      const projectName = this.vars.$prdJson?.projectName || 'Project';
      const dbPath = await this.writeArtifact(`Database/${projectName}.vdb`, dbResult);
      filesWritten.push(dbPath);
      cb.onStepDone?.({ id: 'database', title: 'Database complete' });
      cb.onFileWritten?.(dbPath);

      // Stage 3: ServiceMap
      cb.onStepStart?.({ id: 'serviceMap', title: 'Generating service contracts...' });
      const smResult = await this.callLLM(
        this.buildServiceMapPrompt(),
        { json: true, stream: true, onToken: cb.onToken }
      );
      this.vars.$serviceMapJson = JSON.parse(smResult);
      this.vars.$serviceMapJsonStr = smResult;
      const smPath = await this.writeArtifact('Process/Artifacts/ServiceMap.json', smResult);
      filesWritten.push(smPath);
      cb.onStepDone?.({ id: 'serviceMap', title: 'ServiceMap complete' });
      cb.onFileWritten?.(smPath);

      // Stage 4: UIMap
      cb.onStepStart?.({ id: 'uiMap', title: 'Generating UI architecture...' });
      const uiResult = await this.callLLM(
        this.buildUIMapPrompt(),
        { json: true, stream: true, onToken: cb.onToken }
      );
      this.vars.$uiMapJson = JSON.parse(uiResult);
      this.vars.$uiMapJsonStr = uiResult;
      const uiPath = await this.writeArtifact('Process/Artifacts/UIMap.json', uiResult);
      filesWritten.push(uiPath);
      cb.onStepDone?.({ id: 'uiMap', title: 'UIMap complete' });
      cb.onFileWritten?.(uiPath);

      // Stage 5: Components (parallel)
      const components = this.vars.$uiMapJson?.components || [];
      if (components.length > 0) {
        cb.onStepStart?.({ id: 'components', title: `Generating ${components.length} components...` });
        const cpPaths = await this.parallelGenerate(
          components,
          (item) => this.buildComponentPrompt(item),
          (item) => item.filePath || `ExtComponents/${item.componentId}.cp`,
          cb
        );
        filesWritten.push(...cpPaths);
        cb.onStepDone?.({ id: 'components', title: `${components.length} components done` });
      }

      // Stage 6: Services (parallel)
      const domains = this.vars.$serviceMapJson?.serviceDomains || [];
      if (domains.length > 0) {
        cb.onStepStart?.({ id: 'services', title: `Generating ${domains.length} service domains...` });
        const vsPaths = await this.parallelGenerate(
          domains,
          (item) => this.buildServiceDomainPrompt(item),
          (item) => item.filePath || `Services/${item.domainId}.vs`,
          cb
        );
        filesWritten.push(...vsPaths);
        cb.onStepDone?.({ id: 'services', title: `${domains.length} services done` });
      }

      // Stage 7: Sections (parallel)
      const sections = this.vars.$uiMapJson?.sections || [];
      if (sections.length > 0) {
        cb.onStepStart?.({ id: 'sections', title: `Generating ${sections.length} sections...` });
        const scPaths = await this.parallelGenerate(
          sections,
          (item) => this.buildSectionPrompt(item),
          (item) => item.filePath || `Sections/${item.sectionId}.sc`,
          cb
        );
        filesWritten.push(...scPaths);
        cb.onStepDone?.({ id: 'sections', title: `${sections.length} sections done` });
      }

      // Stage 8: Apps (parallel)
      const apps = this.vars.$uiMapJson?.apps || [];
      if (apps.length > 0) {
        cb.onStepStart?.({ id: 'apps', title: `Generating ${apps.length} apps...` });
        const vxPaths = await this.parallelGenerate(
          apps,
          (item) => this.buildAppPrompt(item),
          (item) => item.filePath || `Apps/${item.appId}.vx`,
          cb
        );
        filesWritten.push(...vxPaths);
        cb.onStepDone?.({ id: 'apps', title: `${apps.length} apps done` });
      }

      // --- Extract ProjectMeta from generated auxiliary files ---
      try {
        const meta = this.extractProjectMeta();
        if (meta) {
          const metaPath = await this.writeArtifact('.vl-code/ProjectMeta.json', JSON.stringify(meta, null, 2));
          cb.onMetadataReady?.(meta);
        }
      } catch { /* non-critical */ }

      cb.onDone?.({ filesWritten, stageCount: 8 });

    } catch (err) {
      cb.onError?.(err.message);
      throw err;
    }
  }

  /** Run a batch of items in parallel through LLM */
  async parallelGenerate(items, promptFn, pathFn, cb) {
    const CONCURRENCY = 3;
    const paths = [];

    for (let i = 0; i < items.length; i += CONCURRENCY) {
      const batch = items.slice(i, i + CONCURRENCY);
      const results = await Promise.all(
        batch.map(async (item) => {
          const prompt = promptFn(item);
          const result = await this.callLLM(prompt, { stream: false });
          const filePath = pathFn(item);
          await this.writeArtifact(filePath, result);
          cb.onFileWritten?.(filePath);
          return filePath;
        })
      );
      paths.push(...results);
    }
    return paths;
  }

  /** Call Claude LLM */
  async callLLM(messages, opts = {}) {
    const jsonRule = opts.json
      ? '- CRITICAL: Return ONLY raw JSON. Do NOT wrap in ```json or ``` code fences. No markdown. Just the JSON object.'
      : '- Return VL source code only, no markdown wrapping.';

    const themeRule = this.config.inlineTheme
      ? `- INLINE THEME MODE: Write ALL style properties (both skeleton AND skin) directly on components. Do NOT reference Theme tokens or .vth files. Use literal CSS values (e.g. color:#2563EB, background-color:#F9FAFB). Include hover/active/disabled states via StateStyle with literal values. No Theme file will be created.`
      : '- Theme tokens use -- prefix (e.g. --colorBrandPrimary)';

    const systemPrompt = `You are a VL language expert code generator.
You generate code strictly following VL ${VL_VERSION} syntax.
Rules:
- Always start files with ${VL_VERSION_HEADER}
- Use dash (-) indentation, NOT spaces
- PascalCase for types/files/components, camelCase for variables/methods
- No escape characters (\\), no semicolons except in FOR loops
${themeRule}
${jsonRule}`;

    const params = {
      model: this.config.model,
      max_tokens: 16000,
      system: systemPrompt,
      messages: Array.isArray(messages) ? messages : [{ role: 'user', content: messages }],
    };

    let result;
    if (opts.stream && opts.onToken) {
      // Streaming mode
      const stream = this.client.messages.stream(params);
      let fullText = '';
      for await (const event of stream) {
        if (event.type === 'content_block_delta' && event.delta?.text) {
          fullText += event.delta.text;
          opts.onToken(event.delta.text);
        }
      }
      result = fullText;
    } else {
      const response = await this.client.messages.create(params);
      result = response.content[0]?.text || '';
    }

    // Strip markdown code fences if present (common LLM mistake)
    if (opts.json) {
      result = this.stripCodeFences(result);
    }

    return result;
  }

  /** Strip markdown code fences from LLM output */
  stripCodeFences(text) {
    let cleaned = text.trim();
    // Remove ```json ... ``` or ``` ... ```
    cleaned = cleaned.replace(/^\s*```(?:json|JSON)?\s*\n?/, '');
    cleaned = cleaned.replace(/\n?\s*```\s*$/, '');
    return cleaned.trim();
  }

  /** Write a file to the project directory */
  async writeArtifact(relativePath, content) {
    const fullPath = path.resolve(this.config.workDir, relativePath);
    await fs.mkdir(path.dirname(fullPath), { recursive: true });
    await fs.writeFile(fullPath, content, 'utf-8');
    return relativePath;
  }

  /**
   * Extract ProjectMeta/1.0 from generated auxiliary files (PRD, ServiceMap, UIMap)
   * Called after stages 1-4 complete. Returns structured metadata for visualization.
   */
  extractProjectMeta() {
    const prd = this.vars.$prdJson;
    const sm = this.vars.$serviceMapJson;
    const ui = this.vars.$uiMapJson;
    if (!prd) return null;

    const meta = {
      $schema: 'VL-ProjectMeta/3.0',
      projectName: prd.projectName || 'Unknown',
      projectDescription: prd.summary || prd.description || '',
      vlVersion: VL_VERSION,
      config: {
        deviceTarget: prd.deviceTarget || 'web',
        vlVersion: VL_VERSION,
      },
      apps: [],
      sections: [],
      components: [],
      services: [],
      dataSchema: { tables: [], relations: [] },
      theme: this.config.inlineTheme ? null : {
        id: 'Theme',
        name: 'Theme',
        filePath: 'Theme/Theme.vth',
        vlVersion: VL_VERSION,
        meta: {},
        slots: {},
      },
    };

    // Extract apps and pages from PRD
    if (prd.apps) {
      for (const app of prd.apps) {
        const appEntry = {
          id: app.appId || app.id || app.name,
          filePath: `Apps/${app.appId || app.name}.vx`,
          pages: [],
        };
        if (app.pages) {
          for (const page of app.pages) {
            appEntry.pages.push({
              id: page.pageId || page.id || page.name,
              sections: (page.sections || []).map(s => typeof s === 'string' ? s : s.sectionId || s),
            });
          }
        }
        meta.apps.push(appEntry);
      }
    }

    // Extract sections from UIMap
    if (ui?.sections) {
      for (const sec of ui.sections) {
        meta.sections.push({
          id: sec.sectionId || sec.id,
          filePath: sec.filePath || `Sections/${sec.sectionId || sec.id}.sc`,
          consumesServices: (sec.serviceBindings || sec.services || []).map((s) => {
            if (typeof s === 'string') return s;
            return s.serviceId || (s.domainId && s.method ? `${s.domainId}.${s.method}` : s.domainId) || null;
          }).filter(Boolean),
          usesComponents: (sec.components || []).map((c) =>
            typeof c === 'string' ? c : c.componentId || c.id || null
          ).filter(Boolean),
        });
      }
    }

    // Extract components from UIMap
    if (ui?.components) {
      for (const cp of ui.components) {
        meta.components.push({
          id: cp.componentId || cp.id,
          filePath: cp.filePath || `ExtComponents/${cp.componentId || cp.id}.cp`,
          props: cp.props || {},
        });
      }
    }

    // Extract services from ServiceMap
    if (sm?.serviceDomains) {
      for (const domain of sm.serviceDomains) {
        const svcEntry = {
          domainId: domain.domainId || domain.id,
          filePath: domain.filePath || `Services/${domain.domainId || domain.id}.vs`,
          methods: (domain.services || []).map(s => ({
            id: s.id || s.name || s.serviceId || null,
            params: s.input ? Object.keys(s.input) : [],
            returns: s.output ? Object.keys(s.output) : [],
          })),
          virtualTables: (domain.virtualTables || []).map(vt => ({
            id: vt.vTableId || vt.id,
            source: vt.source || vt.sourceTable,
            fields: (vt.fields || []).map(f => typeof f === 'string' ? f : f.name || f.field),
          })),
        };
        meta.services.push(svcEntry);
      }
    }

    // Extract tables from PRD dataModel or DB schema
    if (prd.dataModel?.entities) {
      for (const entity of prd.dataModel.entities) {
        meta.dataSchema.tables.push({
          id: entity.entityId || entity.name || entity.id,
          fields: (entity.fields || []).map(f => typeof f === 'string' ? f : f.name || f.field),
        });
      }
      if (prd.dataModel.relations) {
        meta.dataSchema.relations = prd.dataModel.relations;
      }
    }

    return meta;
  }

  // --- Prompt builders ---

  buildPRDPrompt(userRequest, targetLang) {
    return `Analyze the following user requirement and generate a complete PRD.json:

User Requirement:
${userRequest}

Target Language: ${targetLang}

Generate a complete PRD.json following the VL auxiliary file spec.
Include: projectName, deviceTarget, roles, apps (with pages and sections), dataModel (entities and relations), valueDomains (enums and constants), and wiringPlan (interactions).
Return minified JSON.`;
  }

  buildDatabasePrompt() {
    return [
      { role: 'user', content: `Based on this PRD, generate the VL database schema (.vdb file):\n\n${this.vars.$prdJsonStr}` },
      { role: 'user', content: 'Generate a complete .vdb file with tables, fields, indexes, and relations. Start with ${VL_VERSION_HEADER}' },
    ];
  }

  buildServiceMapPrompt() {
    return [
      { role: 'user', content: `PRD:\n${this.vars.$prdJsonStr}\n\nDatabase:\n${this.vars.$vdbContent}` },
      { role: 'user', content: 'Generate the ServiceMap.json with complete service domain contracts, virtual tables, input/output params, and query plans. Return minified JSON.' },
    ];
  }

  buildUIMapPrompt() {
    return [
      { role: 'user', content: `PRD:\n${this.vars.$prdJsonStr}\n\nServiceMap:\n${this.vars.$serviceMapJsonStr}` },
      { role: 'user', content: 'Generate the UIMap.json with complete app definitions, layout trees, section/component definitions, and page bindings. Return minified JSON.' },
    ];
  }

  buildComponentPrompt(item) {
    const themeCtx = this.config.inlineTheme
      ? 'INLINE THEME MODE: Write all skin properties (color, background-color, border-color, box-shadow, opacity, border-radius) directly on components using literal CSS values. No Theme tokens.'
      : `Theme file available.`;
    return [
      { role: 'user', content: `UIMap component spec:\n${JSON.stringify(item)}` },
      { role: 'user', content: `${themeCtx} ServiceMap:\n${this.vars.$serviceMapJsonStr?.substring(0, 2000)}` },
      { role: 'user', content: 'Generate the complete .cp file for this component. Follow VL ${VL_VERSION} syntax strictly.' },
    ];
  }

  buildServiceDomainPrompt(item) {
    return [
      { role: 'user', content: `ServiceMap domain spec:\n${JSON.stringify(item)}` },
      { role: 'user', content: `Database:\n${this.vars.$vdbContent?.substring(0, 3000)}` },
      { role: 'user', content: 'Generate the complete .vs file. Follow VL ${VL_VERSION} syntax strictly.' },
    ];
  }

  buildSectionPrompt(item) {
    const themeNote = this.config.inlineTheme
      ? ' INLINE THEME MODE: Write all skin properties directly using literal CSS values. No Theme tokens.'
      : '';
    return [
      { role: 'user', content: `UIMap section spec:\n${JSON.stringify(item)}` },
      { role: 'user', content: `ServiceMap:\n${this.vars.$serviceMapJsonStr?.substring(0, 3000)}${themeNote}` },
      { role: 'user', content: 'Generate the complete .sc file. Follow VL ${VL_VERSION} syntax strictly.' },
    ];
  }

  buildAppPrompt(item) {
    return [
      { role: 'user', content: `UIMap app spec:\n${JSON.stringify(item)}` },
      { role: 'user', content: `All sections: ${this.vars.$uiMapJson?.sections?.map(s => s.sectionId).join(', ')}` },
      { role: 'user', content: 'Generate the complete .vx app entry file. Follow VL ${VL_VERSION} syntax strictly.' },
    ];
  }
}
