import { Injectable, computed, inject, signal } from '@angular/core';
import { Result } from '@shared/kernel/fp';
import { createStore } from '@shared/application/store';
import { Role, currentRole } from '@shared/infrastructure/role';
import { Brief, allDiagnostics, canSubmit, hasBlockingErrors, unresolvedPlaceholders } from '@brief/domain/brief';
import { BriefMsg, BriefState, initial, reduce } from '@brief/domain/brief.machine';
import { BriefAdapter } from '@brief/infrastructure/brief.adapter';

/**
 * Root singleton for the letter: the Elm store (Model + dispatch), the derived
 * read-model, and the commands (effects) that call the adapter and dispatch the
 * outcome. Mirrors `BigProfileStore`. All of `editable`, `diagnostics`,
 * `unresolved`, `canSubmit` are DERIVED here — never stored.
 */
@Injectable({ providedIn: 'root' })
export class BriefStore {
  private adapter = inject(BriefAdapter);
  private store = createStore<BriefState, BriefMsg>(initial, reduce);

  readonly model = this.store.model;
  readonly role: Role = currentRole();
  readonly busy = signal(false);
  readonly lastError = signal<string | null>(null);
  /** Surfaced autosave state for the indicator + aria-live region. */
  readonly saveState = signal<'idle' | 'saving' | 'saved' | 'error'>('idle');

  private brief = computed<Brief | null>(() => {
    const s = this.model();
    return s.tag === 'loaded' ? s.brief : null;
  });

  /** The single source of truth for edit permission: drafter, and a status the reducer
      treats as editable (draft, or rejected — which reopens to draft on the first edit). */
  readonly editable = computed(() => {
    const b = this.brief();
    return !!b && (b.status.tag === 'draft' || b.status.tag === 'rejected') && this.role === 'drafter';
  });
  readonly diagnostics = computed(() => (this.brief() ? allDiagnostics(this.brief()!) : []));
  readonly unresolved = computed(() => (this.brief() ? unresolvedPlaceholders(this.brief()!) : []));
  /** Submit is allowed only when required sections are filled AND no blocking errors. */
  readonly canSubmit = computed(() => {
    const b = this.brief();
    return !!b && canSubmit(b) && !hasBlockingErrors(this.diagnostics());
  });

  async load() {
    const r = await this.adapter.load();
    if (r.ok) this.store.dispatch({ tag: 'BriefLoaded', brief: r.value.brief, availablePassages: r.value.availablePassages });
    else this.store.dispatch({ tag: 'BriefLoadFailed', reason: r.error });
  }

  /** An edit: apply it optimistically in the pure reducer, then debounce-save. */
  edit(msg: BriefMsg) {
    this.store.dispatch(msg);
    this.scheduleSave();
  }

  private saveTimer?: ReturnType<typeof setTimeout>;
  private scheduleSave() {
    if (!this.editable()) return;
    clearTimeout(this.saveTimer);
    // ponytail: 600ms debounce like the wizard draft-sync; the server is the store of record.
    this.saveTimer = setTimeout(() => void this.flushSave(), 600);
  }
  private async flushSave() {
    const b = this.brief();
    if (!b) return;
    this.saveState.set('saving');
    const r = await this.adapter.save(b.sections);
    if (r.ok) {
      this.saveState.set('saved');
    } else {
      this.lastError.set(r.error);
      this.saveState.set('error');
    }
  }

  /** Demo "start over": recreate the brief server-side and load the fresh view. */
  async resetDemo() {
    this.busy.set(true);
    this.lastError.set(null);
    clearTimeout(this.saveTimer);
    const r = await this.adapter.reset();
    this.busy.set(false);
    this.saveState.set('idle');
    if (r.ok) this.store.dispatch({ tag: 'BriefLoaded', brief: r.value.brief, availablePassages: r.value.availablePassages });
    else this.lastError.set(r.error);
  }

  submit = () => this.transition(() => this.adapter.submit());
  approve = () => this.transition(() => this.adapter.approve());
  reject = (comments: string) => this.transition(() => this.adapter.reject(comments));
  send = () => this.transition(() => this.adapter.send());

  // A transition: flush any pending save, call the server (authoritative), then mirror
  // the returned status through the pure reducer's guarded transition.
  private async transition(action: () => Promise<Result<string, Brief>>) {
    this.busy.set(true);
    this.lastError.set(null);
    clearTimeout(this.saveTimer);
    await this.flushSave();
    const r = await action();
    this.busy.set(false);
    if (!r.ok) {
      this.lastError.set(r.error);
      return;
    }
    this.applyServerStatus(r.value);
  }

  private applyServerStatus(brief: Brief) {
    const s = brief.status;
    switch (s.tag) {
      case 'submitted':
        this.store.dispatch({ tag: 'Submitted', by: s.submittedBy, at: s.submittedAt });
        break;
      case 'approved':
        this.store.dispatch({ tag: 'Approved', by: s.approvedBy, at: s.approvedAt });
        break;
      case 'rejected':
        this.store.dispatch({ tag: 'Rejected', by: s.rejectedBy, at: s.rejectedAt, comments: s.comments });
        break;
      case 'sent':
        this.store.dispatch({ tag: 'Sent', at: s.sentAt });
        break;
      case 'draft':
        // reopened by a save on a rejected letter — reducer already handled it locally.
        break;
    }
  }
}
