/**
 * @file Per-feature state and archive (B). A lane's `stage`/`status`/etc. is
 * the LIVE view of whichever feature it's currently working on; this module
 * lets a lane carry many features across its lifetime by snapshotting the
 * live row into `lane_features` whenever the lane switches (or is cleared),
 * and restoring a feature's saved state when it's switched back to.
 *
 * The `lanes` row itself never changes shape — every existing reader of a
 * lane keeps working unmodified. Only `lanes.active_feature_id` (nullable)
 * is new there, pointing at the currently-live (unarchived) feature row, or
 * null for a lane that has never called `activate`.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

const { db } = require("../db");
const lanesLib = require("./lanes");

const nowIso = () => new Date().toISOString();

/**
 * Canonicalize a feature slug: drop a leading `feat/`, turn `/` and
 * whitespace runs into a single `-`, keep only `[A-Za-z0-9._-]`, collapse
 * repeated `-`, trim leading/trailing `-`. Deliberately does NOT lowercase —
 * a separate function from `worktree.js:slugify` (that one exists for git
 * branch names and lowercases everything), never reused here, never let the
 * two drift onto the same rule by accident.
 *
 * @param {string} input
 * @returns {string}
 * @throws {Error} EBADSLUG when the result is empty.
 */
function canonicalizeSlug(input) {
  const withoutPrefix = String(input || "").replace(/^feat\//, "");
  const result = withoutPrefix
    .replace(/[\s/]+/g, "-")
    .replace(/[^A-Za-z0-9._-]/g, "")
    .replace(/-+/g, "-")
    .replace(/^-+|-+$/g, "");
  if (!result) {
    throw Object.assign(new Error("slug is empty"), { code: "EBADSLUG" });
  }
  return result;
}

function hydrate(row) {
  if (!row) return null;
  let stages = {};
  let links = {};
  try {
    stages = JSON.parse(row.stages || "{}");
  } catch {
    /* corrupt blob -> empty */
  }
  try {
    links = JSON.parse(row.links || "{}");
  } catch {
    /* corrupt blob -> empty */
  }
  return { ...row, stages, links };
}

/** Every feature a lane has ever activated, most recently touched first. */
function listFeatures(laneId) {
  return db
    .prepare("SELECT * FROM lane_features WHERE lane_id = ? ORDER BY updated_at DESC, id DESC")
    .all(laneId)
    .map(hydrate);
}

/** One feature by slug, or null. */
function getFeature(laneId, slug) {
  return hydrate(
    db.prepare("SELECT * FROM lane_features WHERE lane_id = ? AND slug = ?").get(laneId, slug)
  );
}

function getFeatureById(id) {
  return hydrate(db.prepare("SELECT * FROM lane_features WHERE id = ?").get(id));
}

/**
 * Snapshot a lane's CURRENT live bookkeeping into its active feature row
 * (if it has one) and mark that row archived. Returns the archived row, or
 * null when the lane has no active feature — archiving is opt-in, so a lane
 * that never called `activate` is untouched.
 *
 * Does NOT reset the live `lanes` row — that stays the caller's job
 * (`clearLane` resets after archiving; `activateFeature` overwrites the live
 * row with the newly-activated feature's saved state instead of resetting).
 *
 * @param {number} laneId
 * @returns {object|null} The archived feature row.
 */
function archiveActiveFeature(laneId) {
  const lane = lanesLib.getLane(laneId);
  if (!lane || !lane.active_feature_id) return null;
  const active = getFeatureById(lane.active_feature_id);
  if (!active) return null;

  db.prepare(
    `UPDATE lane_features SET
       branch = ?, pipeline = ?, stage = ?, stage_since = ?, status = ?,
       gate_decision = ?, ci_status = ?, qc_dev = ?, stages = ?, links = ?, notes = ?,
       archived_at = ?, updated_at = ?
     WHERE id = ?`
  ).run(
    lane.branch,
    lane.pipeline,
    lane.stage,
    lane.stage_since,
    lane.status,
    lane.gate_decision,
    lane.ci_status,
    active.qc_dev, // qc_dev has no equivalent on `lanes` — carried over from the feature row itself, untouched by the live lane
    JSON.stringify(lane.stages || {}),
    JSON.stringify(lane.links || {}),
    lane.notes,
    nowIso(),
    nowIso(),
    active.id
  );
  return getFeatureById(active.id);
}

/**
 * Activate a feature by slug: archive the currently-active feature (if any,
 * and if it isn't this same slug), find-or-create the target feature row,
 * copy ITS saved bookkeeping onto the live `lanes` row (so switching back to
 * a past feature resumes where it left off — a brand-new slug copies in
 * fresh defaults), and point `lanes.active_feature_id` at it.
 *
 * Re-activating the CURRENTLY active slug is a no-op on the archive step —
 * the live row already IS that feature's state, so there's nothing to
 * restore and nothing to archive.
 *
 * @param {number} laneId
 * @param {string} rawSlug - Canonicalized internally; the caller's raw input is never stored.
 * @param {{title?: string}} [options]
 * @returns {{lane: object, feature: object}}
 */
function activateFeature(laneId, rawSlug, options = {}) {
  const slug = canonicalizeSlug(rawSlug);
  const lane = lanesLib.getLane(laneId);
  if (!lane) throw Object.assign(new Error(`no lane ${laneId}`), { code: "ENOLANE" });

  const current = lane.active_feature_id ? getFeatureById(lane.active_feature_id) : null;
  if (current && current.slug === slug) {
    return { lane, feature: current };
  }

  if (current) archiveActiveFeature(laneId);

  let target = getFeature(laneId, slug);
  if (!target) {
    const info = db
      .prepare(
        `INSERT INTO lane_features (lane_id, slug, title, branch, pipeline, stage, stage_since, status, stages, links, notes)
         VALUES (?, ?, ?, ?, ?, 'idle', ?, 'idle', '{}', '{}', NULL)`
      )
      .run(laneId, slug, options.title || slug, lane.branch, lane.pipeline, nowIso());
    target = getFeatureById(info.lastInsertRowid);
  } else {
    // Un-archive it — it's about to become the live view again.
    db.prepare("UPDATE lane_features SET archived_at = NULL, updated_at = ? WHERE id = ?").run(
      nowIso(),
      target.id
    );
    target = getFeatureById(target.id);
  }

  db.prepare(
    `UPDATE lanes SET
       stage = ?, stage_since = ?, status = ?, gate_decision = ?, ci_status = ?,
       stages = ?, links = ?, notes = ?, active_feature_id = ?, updated_at = ?
     WHERE id = ?`
  ).run(
    target.stage,
    target.stage_since,
    target.status,
    target.gate_decision,
    target.ci_status,
    JSON.stringify(target.stages || {}),
    JSON.stringify(target.links || {}),
    target.notes,
    target.id,
    nowIso(),
    laneId
  );

  return { lane: lanesLib.getLane(laneId), feature: getFeatureById(target.id) };
}

module.exports = {
  canonicalizeSlug,
  listFeatures,
  getFeature,
  activateFeature,
  archiveActiveFeature,
};
