const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');

const FRAMEWORK_DIR = '.framework';
const ROUTINES_SUBPATH = path.join('framework', 'routines');
const LOCK_DIR = '.baldart';
const LOCK_FILE = 'routines.lock.json';

class Routines {
  constructor(cwd = process.cwd()) {
    this.cwd = cwd;
  }

  // -------- Catalog reading ------------------------------------------------

  routinesDir() {
    return path.join(this.cwd, FRAMEWORK_DIR, ROUTINES_SUBPATH);
  }

  catalogPath() {
    return path.join(this.routinesDir(), 'index.yml');
  }

  /** Read the routines catalog (index.yml). Returns {version, routines: []} or null. */
  readCatalog() {
    const p = this.catalogPath();
    if (!fs.existsSync(p)) return null;
    try {
      return yaml.load(fs.readFileSync(p, 'utf8'));
    } catch (err) {
      throw new Error(`Failed to parse ${p}: ${err.message}`);
    }
  }

  /** Read a single routine spec by name. Throws if missing or malformed. */
  readSpec(name) {
    const catalog = this.readCatalog();
    if (!catalog) throw new Error('Routines catalog not found. Is BALDART installed?');
    const entry = (catalog.routines || []).find(r => r.name === name);
    if (!entry) throw new Error(`Unknown routine: ${name}`);
    const specPath = path.join(this.routinesDir(), entry.file);
    if (!fs.existsSync(specPath)) {
      throw new Error(`Routine spec missing on disk: ${specPath}`);
    }
    const spec = yaml.load(fs.readFileSync(specPath, 'utf8'));
    spec._catalogEntry = entry;
    spec._specPath = specPath;
    return spec;
  }

  /** Return the full list of available routines (catalog entries enriched with spec). */
  list() {
    const catalog = this.readCatalog();
    if (!catalog) return [];
    return (catalog.routines || []).map(entry => {
      try {
        return this.readSpec(entry.name);
      } catch (err) {
        return Object.assign({}, entry, { _error: err.message });
      }
    });
  }

  // -------- Lock file ------------------------------------------------------

  lockDir() {
    return path.join(this.cwd, LOCK_DIR);
  }

  lockPath() {
    return path.join(this.lockDir(), LOCK_FILE);
  }

  readLock() {
    const p = this.lockPath();
    if (!fs.existsSync(p)) return { version: 1, routines: {} };
    try {
      return JSON.parse(fs.readFileSync(p, 'utf8'));
    } catch (err) {
      throw new Error(`Failed to parse ${p}: ${err.message}`);
    }
  }

  writeLock(lock) {
    if (!fs.existsSync(this.lockDir())) {
      fs.mkdirSync(this.lockDir(), { recursive: true });
    }
    fs.writeFileSync(this.lockPath(), JSON.stringify(lock, null, 2) + '\n');
  }

  /** Update the lock entry for a routine. Status is "installed" | "skipped" | "disabled". */
  setLockEntry(name, entry) {
    const lock = this.readLock();
    lock.routines = lock.routines || {};
    lock.routines[name] = Object.assign({}, lock.routines[name] || {}, entry, {
      updated_at: new Date().toISOString()
    });
    this.writeLock(lock);
  }

  removeLockEntry(name) {
    const lock = this.readLock();
    if (lock.routines && lock.routines[name]) {
      delete lock.routines[name];
      this.writeLock(lock);
    }
  }

  // -------- Status computation --------------------------------------------

  /**
   * Compute status for every routine in the catalog.
   * Returns array of { spec, lockEntry, status, requiredArtifacts: {present, missing} }
   */
  computeStatus() {
    const lock = this.readLock();
    return this.list().map(spec => {
      if (spec._error) {
        return { spec, lockEntry: null, status: 'error', error: spec._error };
      }
      const lockEntry = (lock.routines || {})[spec.name] || null;
      const required = (spec.required_artifacts || []).map(p => {
        const full = path.join(this.cwd, p);
        return { path: p, exists: fs.existsSync(full) };
      });
      const missingRequired = required.filter(r => !r.exists);
      const isOptional = !!spec.optional;

      let status;
      if (lockEntry && lockEntry.status === 'installed') {
        status = 'installed';
      } else if (lockEntry && lockEntry.status === 'skipped') {
        status = 'skipped';
      } else if (lockEntry && lockEntry.status === 'disabled') {
        status = 'disabled';
      } else if (missingRequired.length > 0 && isOptional) {
        status = 'unavailable';
      } else if (missingRequired.length > 0) {
        status = 'blocked';
      } else {
        status = 'available';
      }

      return {
        spec,
        lockEntry,
        status,
        requiredArtifacts: { present: required.filter(r => r.exists), missing: missingRequired },
        optional: isOptional
      };
    });
  }

  // -------- New routines since last seen ----------------------------------

  /**
   * Returns routines that exist in the catalog but have no lock entry — i.e.
   * routines the user has never been prompted about. Used by add.js/update.js.
   */
  newRoutines() {
    return this.computeStatus().filter(r => r.status === 'available' && !r.lockEntry);
  }
}

module.exports = Routines;
