import { readFile, writeFile } from 'fs/promises';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { parse, stringify } from 'yaml';
import { askLLM, pickModel } from './llm.mjs';
import { parseLLMJson } from './json-utils.mjs';

const __dirname = dirname(fileURLToPath(import.meta.url));
const EVOLUTION_PATH = join(__dirname, 'evolution.yaml');

let state = null;

const DEFAULT_CAPABILITY_SCORES = {
  website_building: 0.2,
  document_authoring: 0.2,
  game_development: 0.2,
  research_synthesis: 0.2,
  media_generation: 0.0,
  office_context: 0.0,
  automation_orchestration: 0.2
};

function createDefaultState() {
  return {
    identity: {
      name_preference: null,
      personality_directives: [],
      operating_mode: 'autonomous-assistant'
    },
    north_star: 'Build a reliable autonomous assistant that ships useful, goal-aligned work products for users.',
    goal: {
      active: null,
      candidates: [],
      last_asked: null
    },
    active_goals: [],
    projects: {},
    capability_scores: { ...DEFAULT_CAPABILITY_SCORES },
    execution_policy: {
      require_goal_for_creation: true,
      require_project_for_creation: true,
      max_parallel_autonomy_tasks: 1,
      autonomy_pause_on_active_chat: true
    },
    quality_policy: {
      min_alignment_score: 70,
      min_usefulness_score: 70,
      retry_on_low_score: true,
      max_revisions: 1
    },
    learning_log: []
  };
}

function slugify(text) {
  return String(text || '')
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')
    .slice(0, 40) || 'goal';
}

function toIsoDate() {
  return new Date().toISOString().slice(0, 10);
}

function nowIso() {
  return new Date().toISOString();
}

function migrateLegacyState(rawState) {
  const defaults = createDefaultState();
  const migrated = { ...defaults };

  if (!rawState || typeof rawState !== 'object') return migrated;

  if (rawState.identity && typeof rawState.identity === 'object') {
    migrated.identity = {
      ...defaults.identity,
      ...rawState.identity
    };
  }
  if (typeof rawState.north_star === 'string' && rawState.north_star.trim()) {
    migrated.north_star = rawState.north_star;
  }

  if (rawState.goal && typeof rawState.goal === 'object') {
    migrated.goal = {
      active: rawState.goal.active || null,
      candidates: Array.isArray(rawState.goal.candidates) ? rawState.goal.candidates.slice(0, 10) : [],
      last_asked: rawState.goal.last_asked || null
    };
  }

  if (Array.isArray(rawState.active_goals)) {
    migrated.active_goals = rawState.active_goals.filter(g => g && g.id && g.title);
  }

  if (rawState.projects && typeof rawState.projects === 'object') {
    migrated.projects = rawState.projects;
  }

  if (!migrated.active_goals.length && migrated.goal.active) {
    const goalId = `goal-${slugify(migrated.goal.active)}-${Date.now()}`;
    migrated.active_goals.push({
      id: goalId,
      title: migrated.goal.active,
      status: 'active',
      created_at: nowIso(),
      updated_at: nowIso(),
      success_criteria: ['Create outputs that clearly support this goal'],
      project_id: null
    });
  }

  // Convert legacy domains graph into a single legacy project so history is not lost.
  if (rawState.domains && typeof rawState.domains === 'object' && Object.keys(migrated.projects).length === 0) {
    const works = Object.entries(rawState.domains).flatMap(([domain, data]) =>
      (data?.works || []).map(name => ({
        filename: `${name}.html`,
        created_at: data?.last_visited || toIsoDate(),
        domain,
        deliverable_type: 'legacy',
        quality_score: null
      }))
    );
    const projectId = `project-legacy-${Date.now()}`;
    migrated.projects[projectId] = {
      id: projectId,
      name: 'Legacy Creative Archive',
      goal_id: migrated.active_goals[0]?.id || null,
      status: 'archived',
      created_at: nowIso(),
      updated_at: nowIso(),
      milestones: ['Preserve prior creative history'],
      next_actions: [],
      focus_domains: Object.keys(rawState.domains),
      artifacts: works.slice(-200)
    };
    if (migrated.active_goals[0]) migrated.active_goals[0].project_id = projectId;
  }

  migrated.capability_scores = {
    ...defaults.capability_scores,
    ...(rawState.capability_scores || {})
  };
  migrated.execution_policy = {
    ...defaults.execution_policy,
    ...(rawState.execution_policy || {})
  };
  migrated.quality_policy = {
    ...defaults.quality_policy,
    ...(rawState.quality_policy || {})
  };
  migrated.learning_log = Array.isArray(rawState.learning_log) ? rawState.learning_log.slice(-200) : [];

  return migrated;
}

function getActiveGoalObject() {
  if (!state) return null;
  return state.active_goals.find(g => g.status === 'active') || null;
}

function ensureProjectForGoal(goalObj) {
  if (!goalObj) return null;
  if (goalObj.project_id && state.projects[goalObj.project_id]) return state.projects[goalObj.project_id];

  const projectId = `project-${slugify(goalObj.title)}-${Date.now()}`;
  state.projects[projectId] = {
    id: projectId,
    name: `${goalObj.title} Project`,
    goal_id: goalObj.id,
    status: 'active',
    created_at: nowIso(),
    updated_at: nowIso(),
    milestones: [
      'Define concrete deliverables',
      'Ship first useful artifact',
      'Collect feedback and iterate'
    ],
    next_actions: [
      'Complete one concrete user-facing deliverable tied to a recent request',
      'Improve one existing artifact in the workspace with measurable value'
    ],
    focus_domains: ['websites', 'documents', 'automation'],
    artifacts: []
  };
  goalObj.project_id = projectId;
  goalObj.updated_at = nowIso();
  return state.projects[projectId];
}

function incrementCapability(capability, amount = 0.03) {
  if (!capability || !(capability in state.capability_scores)) return;
  const current = Number(state.capability_scores[capability] || 0);
  state.capability_scores[capability] = Math.max(0, Math.min(1, Number((current + amount).toFixed(3))));
}

function mapArtifactToCapability({ filename, deliverableType, domain }) {
  const lowerType = String(deliverableType || '').toLowerCase();
  const lowerDomain = String(domain || '').toLowerCase();
  const lowerFile = String(filename || '').toLowerCase();

  if (lowerType.includes('game') || lowerDomain.includes('game')) return 'game_development';
  if (lowerType.includes('media') || lowerDomain.includes('media')) return 'media_generation';
  if (lowerType.includes('office') || lowerDomain.includes('office') || lowerDomain.includes('spreadsheet')) return 'office_context';
  if (lowerType.includes('research') || lowerDomain.includes('research')) return 'research_synthesis';
  if (lowerType.includes('automation') || lowerDomain.includes('automation')) return 'automation_orchestration';
  if (lowerFile.endsWith('.md') || lowerType.includes('document') || lowerType.includes('doc')) return 'document_authoring';
  return 'website_building';
}

export async function loadEvolution() {
  try {
    const raw = await readFile(EVOLUTION_PATH, 'utf-8');
    const parsed = parse(raw);
    state = migrateLegacyState(parsed);
  } catch {
    state = createDefaultState();
    await saveEvolution();
  }
  return state;
}

export async function saveEvolution() {
  if (!state) return;
  const raw = stringify(state, { lineWidth: 120 });
  await writeFile(EVOLUTION_PATH, raw, 'utf-8');
}

export function getGoal() {
  if (!state) return null;
  const active = getActiveGoalObject();
  if (active?.title) return active.title;
  return state.goal?.active || null;
}

export async function setGoal(goal) {
  if (!state) await loadEvolution();
  const title = String(goal || '').trim();
  if (!title) return;

  for (const g of state.active_goals) {
    if (g.status === 'active') g.status = 'paused';
  }

  const goalObj = {
    id: `goal-${slugify(title)}-${Date.now()}`,
    title,
    status: 'active',
    created_at: nowIso(),
    updated_at: nowIso(),
    success_criteria: [
      'Produce clear user-facing value',
      'Deliver artifacts that are reusable and coherent'
    ],
    project_id: null
  };
  state.active_goals.unshift(goalObj);
  if (state.active_goals.length > 20) state.active_goals = state.active_goals.slice(0, 20);

  state.goal.active = title;
  ensureProjectForGoal(goalObj);
  await saveEvolution();
}

export async function addGoalCandidate(candidate) {
  if (!state) await loadEvolution();
  const text = String(candidate || '').trim();
  if (!text) return;
  if (!state.goal.candidates.includes(text)) {
    state.goal.candidates.push(text);
    if (state.goal.candidates.length > 12) state.goal.candidates = state.goal.candidates.slice(-12);
    await saveEvolution();
  }
}

export async function decideNextMove() {
  if (!state) await loadEvolution();

  let activeGoal = getActiveGoalObject();
  if (!activeGoal) {
    const fallback = state.goal.active || 'Execute clear user-requested deliverables and improve workspace artifacts with measurable value.';
    await setGoal(fallback);
    activeGoal = getActiveGoalObject();
  }
  const project = ensureProjectForGoal(activeGoal);

  const artifactCount = (project.artifacts || []).length;
  const rawNextAction = (project.next_actions || [])[0] || 'Create a concrete artifact that advances the project.';
  const genericActionPattern = /\b(project brief|capabilities?\s+blueprint|delivery\s+protocol|loop\s*#?)\b/i;
  const nextAction = genericActionPattern.test(rawNextAction)
    ? 'Deliver one concrete user-facing artifact tied to the latest user request.'
    : rawNextAction;
  const recentArtifacts = (project.artifacts || []).slice(-8);
  const docCount = recentArtifacts.filter(a => String(a.deliverable_type || '').toLowerCase().includes('doc') || String(a.filename || '').toLowerCase().endsWith('.md')).length;
  const pageCount = recentArtifacts.length - docCount;
  let deliverableType = artifactCount === 0 ? 'document' : artifactCount % 2 === 0 ? 'website' : 'document';
  if (docCount > pageCount + 1) deliverableType = 'website';
  if (pageCount > docCount + 1) deliverableType = 'document';
  const mode = artifactCount < 2 ? 'bootstrap' : 'deliver';

  const context = `## Goal-Driven Execution
Goal ID: ${activeGoal.id}
Active goal: ${activeGoal.title}
Project ID: ${project.id}
Project: ${project.name}
Suggested next action: ${nextAction}
Required deliverable type: ${deliverableType}

## Policy
- Every autonomous creation MUST map to this goal/project.
- Prioritize useful artifacts over novelty.
- Avoid random experiments unless they directly support deliverables.
- If user requests are unclear, prefer waiting or refining existing artifacts over generating speculative new files.
- Meta strategy artifacts (blueprints/protocols/loop notes) require explicit user request.

## Success Criteria
${(activeGoal.success_criteria || []).map(s => `- ${s}`).join('\n')}

## Recent Project Artifacts
${(project.artifacts || []).slice(-6).map(a => `- ${a.filename} (${a.deliverable_type || 'artifact'})`).join('\n') || '- none yet'}
`;

  return {
    mode,
    context,
    goalId: activeGoal.id,
    projectId: project.id,
    deliverableType,
    qualityTarget: state.quality_policy.min_alignment_score
  };
}

export async function recordCreation({
  filename,
  mode,
  domain,
  connections,
  goalId,
  projectId,
  deliverableType,
  qualityScore = null
}) {
  if (!state) await loadEvolution();

  let activeGoal = getActiveGoalObject();
  if (goalId) {
    const match = state.active_goals.find(g => g.id === goalId);
    if (match) activeGoal = match;
  }
  if (!activeGoal) return;

  let project = null;
  if (projectId && state.projects[projectId]) {
    project = state.projects[projectId];
  } else {
    project = ensureProjectForGoal(activeGoal);
  }

  const artifact = {
    filename,
    created_at: nowIso(),
    deliverable_type: deliverableType || (String(filename).toLowerCase().endsWith('.md') ? 'document' : 'website'),
    domain: domain || null,
    connections: connections || [],
    quality_score: qualityScore
  };
  project.artifacts = project.artifacts || [];
  project.artifacts.push(artifact);
  if (project.artifacts.length > 500) project.artifacts = project.artifacts.slice(-500);
  project.updated_at = nowIso();

  const cap = mapArtifactToCapability(artifact);
  incrementCapability(cap, 0.03);
  state.learning_log.push({
    at: nowIso(),
    event: 'artifact_created',
    goal_id: activeGoal.id,
    project_id: project.id,
    filename,
    mode: mode || null,
    capability: cap,
    quality_score: qualityScore
  });
  if (state.learning_log.length > 300) state.learning_log = state.learning_log.slice(-300);

  await saveEvolution();
}

export async function getEvolutionContext() {
  if (!state) await loadEvolution();
  const activeGoal = getActiveGoalObject();
  const project = activeGoal ? ensureProjectForGoal(activeGoal) : null;
  const capabilityList = Object.entries(state.capability_scores || {})
    .sort((a, b) => b[1] - a[1])
    .map(([k, v]) => `- ${k}: ${Math.round(v * 100)}%`)
    .join('\n');

  return `## Evolution State V2
North star: ${state.north_star}
Active goal: ${activeGoal?.title || '(none)'}
Goal ID: ${activeGoal?.id || '(none)'}
Project ID: ${project?.id || '(none)'}
Project: ${project?.name || '(none)'}
Project status: ${project?.status || '(none)'}

### Execution Policy
- Require goal for creation: ${state.execution_policy.require_goal_for_creation}
- Require project for creation: ${state.execution_policy.require_project_for_creation}
- Max parallel autonomy tasks: ${state.execution_policy.max_parallel_autonomy_tasks}

### Quality Policy
- Minimum alignment score: ${state.quality_policy.min_alignment_score}
- Minimum usefulness score: ${state.quality_policy.min_usefulness_score}

### Capability Scores
${capabilityList || '- none'}

### Recent Learning Log
${(state.learning_log || []).slice(-8).map(l => `- ${l.at}: ${l.event} (${l.filename || ''})`).join('\n') || '- none'}
`;
}

export function shouldAskAboutGoals() {
  if (!state) return false;
  if (getActiveGoalObject()) return false;
  const lastAsked = state.goal?.last_asked;
  if (!lastAsked) return true;
  const hoursSince = (Date.now() - new Date(lastAsked).getTime()) / (1000 * 60 * 60);
  return hoursSince > 24;
}

export async function markGoalAsked() {
  if (!state) await loadEvolution();
  state.goal.last_asked = nowIso();
  await saveEvolution();
}

export function getQualityPolicy() {
  if (!state) return createDefaultState().quality_policy;
  return state.quality_policy || createDefaultState().quality_policy;
}

export async function assessOutputQuality(filename, content, goal = null) {
  const prompt = `Assess the quality of this output artifact.
Filename: ${filename}
Goal: ${goal || '(none)'}
Content (first 5000 chars):
${String(content).slice(0, 5000)}

Return JSON:
{
  "score": 0-100,
  "feedback": "specific feedback on what works and what doesn't",
  "improvementSuggestions": ["specific actionable improvement"]
}`;

  try {
    const response = await askLLM(prompt, { model: pickModel('reflect') });
    const parsed = parseLLMJson(response);
    if (!parsed.ok) return { score: 50, feedback: 'Could not assess quality', improvementSuggestions: [] };
    return {
      score: parsed.data.score || 50,
      feedback: parsed.data.feedback || '',
      improvementSuggestions: parsed.data.improvementSuggestions || []
    };
  } catch (err) {
    return { score: 50, feedback: `Assessment failed: ${err.message}`, improvementSuggestions: [] };
  }
}

export async function compareOutputs(contentA, contentB, criteria = 'overall quality and usefulness') {
  const prompt = `Compare these two outputs on: ${criteria}

Output A (first 3000 chars):
${String(contentA).slice(0, 3000)}

Output B (first 3000 chars):
${String(contentB).slice(0, 3000)}

Return JSON: { "winner": "A" or "B" or "tie", "reasoning": "why", "scores": { "a": 0-100, "b": 0-100 } }`;

  try {
    const response = await askLLM(prompt, { model: pickModel('reflect') });
    const parsed = parseLLMJson(response);
    if (!parsed.ok) return { winner: 'tie', reasoning: 'Could not compare', scores: { a: 50, b: 50 } };
    return {
      winner: parsed.data.winner || 'tie',
      reasoning: parsed.data.reasoning || '',
      scores: parsed.data.scores || { a: 50, b: 50 }
    };
  } catch (err) {
    return { winner: 'tie', reasoning: `Comparison failed: ${err.message}`, scores: { a: 50, b: 50 } };
  }
}

export function getCapabilityOntology() {
  if (!state) return {};
  const ontology = {};
  for (const [key, value] of Object.entries(state.capability_scores || {})) {
    if (typeof value === 'number') {
      ontology[key] = { score: value, sub: {} };
    } else if (typeof value === 'object' && value !== null) {
      ontology[key] = {
        score: value.score || 0,
        sub: value.sub || {}
      };
    }
  }
  return ontology;
}
