const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('yaml');
const { Manifest } = require('./manifest');

/**
 * Immutable snapshot of an existing BMAD installation.
 * Pure query object — no filesystem operations after construction.
 */
class ExistingInstall {
  #version;

  constructor({ installed, version, hasCore, modules, ides, customModules }) {
    this.installed = installed;
    this.#version = version;
    this.hasCore = hasCore;
    this.modules = Object.freeze(modules.map((m) => Object.freeze({ ...m })));
    this.moduleIds = Object.freeze(this.modules.map((m) => m.id));
    this.ides = Object.freeze([...ides]);
    this.customModules = Object.freeze([...customModules]);
    Object.freeze(this);
  }

  get version() {
    if (!this.installed) {
      throw new Error('version is not available when nothing is installed');
    }
    return this.#version;
  }

  static empty() {
    return new ExistingInstall({
      installed: false,
      version: null,
      hasCore: false,
      modules: [],
      ides: [],
      customModules: [],
    });
  }

  /**
   * Scan a bmad directory and return an immutable snapshot of what's installed.
   * @param {string} bmadDir - Path to bmad directory
   * @returns {Promise<ExistingInstall>}
   */
  static async detect(bmadDir) {
    if (!(await fs.pathExists(bmadDir))) {
      return ExistingInstall.empty();
    }

    let version = null;
    let hasCore = false;
    const modules = [];
    let ides = [];
    let customModules = [];

    const manifest = new Manifest();
    const manifestData = await manifest.read(bmadDir);
    if (manifestData) {
      version = manifestData.version;
      if (manifestData.customModules) {
        customModules = manifestData.customModules;
      }
      if (manifestData.ides) {
        ides = manifestData.ides.filter((ide) => ide && typeof ide === 'string');
      }
    }

    const corePath = path.join(bmadDir, 'core');
    if (await fs.pathExists(corePath)) {
      hasCore = true;

      if (!version) {
        const coreConfigPath = path.join(corePath, 'config.yaml');
        if (await fs.pathExists(coreConfigPath)) {
          try {
            const configContent = await fs.readFile(coreConfigPath, 'utf8');
            const config = yaml.parse(configContent);
            if (config.version) {
              version = config.version;
            }
          } catch {
            // Ignore config read errors
          }
        }
      }
    }

    if (manifestData && manifestData.modules && manifestData.modules.length > 0) {
      for (const moduleId of manifestData.modules) {
        const modulePath = path.join(bmadDir, moduleId);
        const moduleConfigPath = path.join(modulePath, 'config.yaml');

        const moduleInfo = {
          id: moduleId,
          path: modulePath,
          version: 'unknown',
        };

        if (await fs.pathExists(moduleConfigPath)) {
          try {
            const configContent = await fs.readFile(moduleConfigPath, 'utf8');
            const config = yaml.parse(configContent);
            moduleInfo.version = config.version || 'unknown';
            moduleInfo.name = config.name || moduleId;
            moduleInfo.description = config.description;
          } catch {
            // Ignore config read errors
          }
        }

        modules.push(moduleInfo);
      }
    }

    const installed = hasCore || modules.length > 0 || !!manifestData;

    if (!installed) {
      return ExistingInstall.empty();
    }

    return new ExistingInstall({ installed, version, hasCore, modules, ides, customModules });
  }
}

module.exports = { ExistingInstall };
