import os from 'os';
import path from 'path';
import { mkdir, readFile, writeFile } from 'fs/promises';

class SaveState {
  constructor() {
    this._state = null;
    this._savePath = null;
    this._writeQueue = Promise.resolve();
  }

  async load() {
    const savePath = this._getSavePath();

    try {
      const raw = await readFile(savePath, 'utf8');
      this._state = this._applyDefaults(JSON.parse(raw));
      return this._state;
    } catch (error) {
      if (error?.code !== 'ENOENT') {
        throw error;
      }

      const initialState = this._defaultState();
      this._state = initialState;
      await this.write(initialState);
      return this._state;
    }
  }

  async write(state) {
    const savePath = this._getSavePath();
    const nextState = this._clone(state ?? this._state ?? this._defaultState());

    nextState.last_saved = new Date().toISOString();
    if (!nextState.created_at) {
      nextState.created_at = nextState.last_saved;
    }

    await mkdir(path.dirname(savePath), { recursive: true });
    await writeFile(savePath, `${JSON.stringify(nextState, null, 2)}\n`, 'utf8');
    this._state = nextState;
    return this._state;
  }

  get() {
    return this._state;
  }

  set(partial) {
    if (!this._state) {
      this._state = this._defaultState();
    }

    const nextState = { ...this._state };

    for (const [key, value] of Object.entries(partial)) {
      if (key === 'mail' || key === 'certifications') {
        nextState[key] = Array.isArray(value) ? this._clone(value) : value;
        continue;
      }

      if (this._isPlainObject(value) && this._isPlainObject(nextState[key])) {
        nextState[key] = { ...nextState[key], ...value };
        continue;
      }

      nextState[key] = Array.isArray(value) ? this._clone(value) : value;
    }

    this._state = nextState;
    this._queueWrite();
    return this._state;
  }

  _queueWrite() {
    const snapshot = this._clone(this._state);
    this._writeQueue = this._writeQueue
      .then(() => this.write(snapshot))
      .catch((error) => {
        console.error('Failed to persist save state:', error);
      });
    return this._writeQueue;
  }

  _getSavePath() {
    if (!this._savePath) {
      const configuredDir = process.env.SAVE_DIR ?? '~/.local/share/sysadmin-chronicles';
      const expandedDir = this._expandHome(configuredDir);
      this._savePath = path.join(expandedDir, 'save.json');
    }

    return this._savePath;
  }

  _expandHome(value) {
    if (value === '~') {
      return os.homedir();
    }

    if (value.startsWith('~/')) {
      return path.join(os.homedir(), value.slice(2));
    }

    return value;
  }

  _defaultState() {
    const now = new Date().toISOString();
    return {
      schema_version: 3,
      created_at: now,
      last_saved: now,
      trust: 50.0,
      shift_number: 1,
      shift_started_at: now,
      world_flags: [],
      progression: {
        unlocked_access: [],
        unlocked_vms: [],
        unlocked_docs: []
      },
      quests: {},
      tickets: {},
      mail: [],
      certifications: [],
      current_shift_stats: {
        assigned_ticket_ids: [],
        resolved_tickets: [],
        flagged_issues: []
      },
      shift_history: [],
      pressure: {},
      incidents: {},
      sage: {
        hint_counts: {}
      },
      behavior: { curiosity: 50, obedience: 50, risk: 50, suspicion: 0 },
      narrative_phase: 'normal_work',
      hidden_hooks_discovered: [],
      player_portrait: 'player-silhouette'
    };
  }

  _isPlainObject(value) {
    return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
  }

  _applyDefaults(state) {
    return this._mergeWithDefaults(this._defaultState(), state ?? {});
  }

  _mergeWithDefaults(defaults, value) {
    if (Array.isArray(defaults)) {
      return Array.isArray(value) ? this._clone(value) : this._clone(defaults);
    }

    if (!this._isPlainObject(defaults)) {
      return value ?? defaults;
    }

    const merged = {};
    const source = this._isPlainObject(value) ? value : {};

    for (const [key, defaultValue] of Object.entries(defaults)) {
      merged[key] = this._mergeWithDefaults(defaultValue, source[key]);
    }

    for (const [key, sourceValue] of Object.entries(source)) {
      if (!(key in merged)) {
        merged[key] = Array.isArray(sourceValue) ? this._clone(sourceValue) : sourceValue;
      }
    }

    return merged;
  }

  _clone(value) {
    return JSON.parse(JSON.stringify(value));
  }
}

export const saveState = new SaveState();
