// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only

/**
 * WU Paths - Centralized path management for Work Units
 *
 * All paths are configurable via workspace.yaml (`software_delivery`) config.
 *
 * @module wu-paths
 */

import * as path from 'node:path';

import { findProjectRoot } from '@lumenflow/kernel/primitives/project-root';

import {
  getProjectRoot as getProjectRootFromConstants,
  toKebab,
} from '../constants/wu-domain-constants.js';
import {
  getDirectories,
  getStatePaths,
  type WorkspaceDirectories,
  type WorkspaceStatePaths,
} from '../config/workspace-reader.js';

/**
 * WU-2689 (INIT-058 L4): Pack-local project root resolver.
 *
 * Uses the kernel's `findProjectRoot(cwd)` primitive (which walks up from `cwd`
 * looking for `workspace.yaml` / `package.json`). Falls back to `process.cwd()`
 * if resolution fails — matches the behaviour of the core helper this replaces.
 */
function getProjectRootFromConfig(options: { projectRoot?: string } = {}): string {
  if (options.projectRoot) {
    return options.projectRoot;
  }
  try {
    return findProjectRoot(process.cwd());
  } catch {
    return process.cwd();
  }
}

/**
 * WU-2689: Resolve the typed directory + state-path bundle the rest of this
 * module was previously sourcing from `getConfig()`. Threads `projectRoot`
 * through to the pack's workspace-reader so callers can override the root
 * (tests, multi-project tooling).
 */
/**
 * WU-2689: Directory defaults used when the pack workspace-reader fails to
 * locate a workspace.yaml (e.g. tmpdir-based tests, sandbox environments).
 *
 * Kept in sync with `DIRECTORY_DEFAULTS` in the pack workspace-reader.
 */
const FALLBACK_DIRECTORIES: WorkspaceDirectories = {
  appsWeb: '',
  worktrees: 'worktrees/',
  ai: 'ai/',
  claude: '.claude/',
  docs: 'docs/',
  packages: 'packages/',
  tools: 'tools/',
  memoryBank: 'memory-bank/',
  wuDir: 'docs/operations/tasks/wu',
  initiativesDir: 'docs/operations/tasks/initiatives',
  backlogPath: 'docs/operations/tasks/backlog.md',
  statusPath: 'docs/operations/tasks/status.md',
  skillsDir: '.claude/skills',
  agentsDir: '.claude/agents',
  adrDir: 'docs/adr',
  plansDir: 'docs/operations/plans',
  templatesDir: '.lumenflow/templates',
  onboardingDir: 'docs/operations/_frameworks/lumenflow/agent/onboarding',
  completeGuidePath: 'LUMENFLOW.md',
  quickRefPath: 'docs/operations/_frameworks/lumenflow/agent/onboarding/quick-ref-commands.md',
  startingPromptPath: 'docs/operations/_frameworks/lumenflow/agent/onboarding/starting-prompt.md',
  sizingGuidePath: 'docs/operations/_frameworks/lumenflow/wu-sizing-guide.md',
  governancePath: 'docs/operations/_frameworks/lumenflow/governance.md',
  safeGitPath: 'scripts/safe-git',
};

const FALLBACK_STATE_PATHS: WorkspaceStatePaths = {
  base: '.lumenflow',
  stateDir: '.lumenflow/state',
  archiveDir: '.lumenflow/archive',
  stampsDir: '.lumenflow/stamps',
  mergeLock: '.lumenflow/merge.lock',
  telemetry: '.lumenflow/telemetry',
  flowLog: '.lumenflow/flow.log',
  sessions: '.lumenflow/sessions',
  incidents: '.lumenflow/incidents',
  commandsLog: '.lumenflow/commands.log',
  eventArchival: {
    archiveAfter: 90 * 24 * 60 * 60 * 1000,
    keepArchives: true,
  },
};

function resolveWorkspacePaths(options: { projectRoot?: string } = {}): {
  directories: WorkspaceDirectories;
  state: WorkspaceStatePaths;
} {
  const cwd = options.projectRoot ?? getProjectRootFromConfig();
  // WU-2689: tolerate missing workspace.yaml (e.g. tmpdir-based tests) by
  // falling back to built-in defaults when the pack workspace-reader throws
  // (ENOENT on workspace.yaml or findProjectRoot exhaustion). Matches the
  // graceful behaviour of the former `@lumenflow/core` `getConfig({ projectRoot })`
  // path, which returned defaults for non-existent workspace configs.
  let directories: WorkspaceDirectories;
  let state: WorkspaceStatePaths;
  try {
    directories = getDirectories(cwd);
  } catch {
    directories = FALLBACK_DIRECTORIES;
  }
  try {
    state = getStatePaths(cwd);
  } catch {
    state = FALLBACK_STATE_PATHS;
  }
  return { directories, state };
}

/**
 * Resolve repo root from an absolute file path by traversing up N directory levels.
 *
 * @param absolutePath - Absolute path to a file within the repo
 * @param depth - Number of directory levels to traverse up
 * @returns Absolute path to repo root
 */
export function resolveRepoRoot(absolutePath: string, depth: number): string {
  let result = absolutePath;
  for (let i = 0; i < depth; i++) {
    result = path.dirname(result);
  }
  return result;
}

/**
 * Compute the directory depth of a relative path (number of path segments).
 *
 * WU-1523: Used to dynamically determine backlog depth from config
 * instead of fixed depth assumptions. This ensures getStateStoreDirFromBacklog
 * works correctly for configured docs structures.
 *
 * @param relativePath - Relative file path (e.g., '<configured backlogPath>')
 * @returns Number of path segments for the configured path
 */
function computePathDepth(relativePath: string): number {
  // Normalize separators and split on path separator
  const normalized = relativePath.replace(/\\/g, '/');
  return normalized.split('/').filter(Boolean).length;
}

/**
 * Get the state store directory path from backlog.md path.
 *
 * WU-1523: Now computes depth dynamically from configured backlog path
 * instead of using fixed depth values. This fixes empty backlog.md
 * and status.md rendering in scaffolded projects with non-default docs structures.
 *
 * @param backlogPath - Absolute path to backlog.md
 * @returns Absolute path to state store directory
 */
export function getStateStoreDirFromBacklog(backlogPath: string): string {
  const { directories, state } = resolveWorkspacePaths();
  const depth = computePathDepth(directories.backlogPath);
  const repoRoot = resolveRepoRoot(backlogPath, depth);
  return path.join(repoRoot, state.stateDir);
}

/**
 * Create WU paths object with configurable base paths
 *
 * @param options - Options for path generation
 * @param options.projectRoot - Override project root
 * @returns WU paths object
 */
export function createWuPaths(options: { projectRoot?: string } = {}) {
  const { directories, state } = resolveWorkspacePaths(options);

  return {
    /**
     * Get path to WU YAML file
     * @param id - WU ID (e.g., 'WU-123')
     * @returns Path to WU YAML file
     */
    WU: (id: string) => path.join(directories.wuDir, `${id}.yaml`),

    /**
     * Get path to WU directory
     * @returns Path to WU directory
     */
    WU_DIR: () => directories.wuDir,

    /**
     * Get path to status.md
     * @returns Path to status.md
     */
    STATUS: () => directories.statusPath,

    /**
     * Get path to backlog.md
     * @returns Path to backlog.md
     */
    BACKLOG: () => directories.backlogPath,

    /**
     * Get path to stamps directory
     * @returns Path to stamps directory
     */
    STAMPS_DIR: () => state.stampsDir,

    /**
     * Get path to WU done stamp file
     * @param id - WU ID (e.g., 'WU-123')
     * @returns Path to stamp file
     */
    STAMP: (id: string) => path.join(state.stampsDir, `${id}.done`),

    /**
     * Get path to state directory
     * @returns Path to state directory
     */
    STATE_DIR: () => state.stateDir,

    /**
     * Get path to initiatives directory
     * @returns Path to initiatives directory
     */
    INITIATIVES_DIR: () => directories.initiativesDir,

    /**
     * Get path to initiative YAML file
     * @param id - Initiative ID (e.g., 'INIT-001')
     * @returns Path to initiative YAML file
     */
    INITIATIVE: (id: string) => path.join(directories.initiativesDir, `${id}.yaml`),

    /**
     * Get path to worktrees directory
     * @returns Path to worktrees directory
     */
    WORKTREES_DIR: () => directories.worktrees,

    /**
     * Get path to a specific worktree directory
     * @param lane - Lane name (e.g., 'Framework: Core')
     * @param id - WU ID (e.g., 'WU-123')
     * @returns Path to worktree directory
     */
    WORKTREE: (lane: string, id: string) =>
      path.join(directories.worktrees, `${toKebab(lane)}-${id.toLowerCase()}`),

    /**
     * Get path to plans directory
     * @returns Path to plans directory (WU-1301)
     */
    PLANS_DIR: () => directories.plansDir,

    /**
     * Get path to templates directory
     * @returns Path to templates directory (WU-1310)
     */
    TEMPLATES_DIR: () => directories.templatesDir,

    /**
     * Get path to onboarding directory
     * @returns Path to onboarding directory (WU-1310)
     */
    ONBOARDING_DIR: () => directories.onboardingDir,

    /**
     * Get path to LumenFlow complete guide
     * @returns Path to complete guide markdown
     */
    COMPLETE_GUIDE_PATH: () => directories.completeGuidePath,

    /**
     * Get path to onboarding quick reference commands
     * @returns Path to quick reference markdown
     */
    QUICK_REF_PATH: () => directories.quickRefPath,

    /**
     * Get path to onboarding starting prompt
     * @returns Path to starting prompt markdown
     */
    STARTING_PROMPT_PATH: () => directories.startingPromptPath,

    /**
     * Get path to WU sizing guide
     * @returns Path to sizing guide markdown
     */
    SIZING_GUIDE_PATH: () => directories.sizingGuidePath,

    /**
     * Get path to governance document
     * @returns Path to governance markdown
     */
    GOVERNANCE_PATH: () => directories.governancePath,
  };
}

/**
 * Default WU paths using default config
 * For backwards compatibility with existing code
 */
export const WU_PATHS = createWuPaths();

/**
 * Generate default worktree path from WU document
 *
 * @param doc - WU document with lane and id fields
 * @returns Worktree path or null if inputs are invalid
 */
export function defaultWorktreeFrom(
  doc: { lane?: string; id?: string } | null | undefined,
): string | null {
  if (!doc) return null;
  const lane = doc.lane;
  const id = doc.id;

  // Validate inputs
  if (!lane || !id) return null;

  // Convert to string and trim
  const laneStr = String(lane).trim();
  const idStr = String(id).trim();

  // Check for empty strings after trimming
  if (laneStr === '' || idStr === '') return null;

  return createWuPaths().WORKTREE(laneStr, idStr);
}

/**
 * Resolve a repo-root-relative path to an absolute path using project root.
 *
 * @param relativePath - Path relative to project root
 * @param moduleUrl - import.meta.url of the calling module (for backwards compat)
 * @returns Absolute path resolved from project root
 */
export function resolveFromProjectRoot(relativePath: string, moduleUrl?: string): string {
  // Try config-based project root first, fall back to constants-based
  let projectRoot: string;
  try {
    projectRoot = getProjectRootFromConfig();
  } catch {
    projectRoot = moduleUrl ? getProjectRootFromConstants(moduleUrl) : process.cwd();
  }
  return path.join(projectRoot, relativePath);
}

/**
 * Get absolute path to WU YAML file
 *
 * @param id - WU ID (e.g., 'WU-123')
 * @param options - Options
 * @returns Absolute path to WU YAML file
 */
export function getAbsoluteWuPath(id: string, options: { projectRoot?: string } = {}): string {
  const projectRoot = options.projectRoot || getProjectRootFromConfig();
  const paths = createWuPaths({ projectRoot });
  return path.join(projectRoot, paths.WU(id));
}

/**
 * Get absolute path to stamp file
 *
 * @param id - WU ID (e.g., 'WU-123')
 * @param options - Options
 * @returns Absolute path to stamp file
 */
export function getAbsoluteStampPath(id: string, options: { projectRoot?: string } = {}): string {
  const projectRoot = options.projectRoot || getProjectRootFromConfig();
  const paths = createWuPaths({ projectRoot });
  return path.join(projectRoot, paths.STAMP(id));
}
