import { effect, Injectable, signal, WritableSignal } from '@angular/core';
import { Tag } from '../../tag/tag.model';
import { AddTaskBarState, INITIAL_ADD_TASK_BAR_STATE } from './add-task-bar.const';
import { toObservable } from '@angular/core/rxjs-interop';
import { SS } from '../../../core/persistence/storage-keys.const';
import { TimeSpentOnDay, TaskReminderOptionId } from '../task.model';
import { TaskAttachment } from '../task-attachment/task-attachment.model';
import { RepeatQuickSetting } from '../../task-repeat-cfg/task-repeat-cfg.model';
import { normalizeClockStr } from '../../../util/normalize-clock-str';

@Injectable()
export class AddTaskBarStateService {
  private readonly _taskInputState: WritableSignal<AddTaskBarState> = signal({
    ...INITIAL_ADD_TASK_BAR_STATE,
  });

  readonly inputTxt = signal(sessionStorage.getItem(SS.ADD_TASK_BAR_TXT) || '');
  readonly inputTxt$ = toObservable(this.inputTxt);
  readonly state = this._taskInputState.asReadonly();
  readonly isAutoDetected = signal(false);

  // Free-form note/description entered alongside the task. Kept separate from
  // the parsed `inputTxt` state since it is prose, not short-syntax tokens.
  // Persisted like `inputTxt` so a draft note survives closing/reopening the
  // bar (e.g. an Escape in the title input) and a reload, instead of being lost.
  readonly noteTxt = signal(sessionStorage.getItem(SS.ADD_TASK_BAR_NOTE) || '');
  // Start expanded when reopening with a persisted draft note, so it is visible
  // rather than hidden behind the collapsed toggle.
  readonly isNoteExpanded = signal(!!this.noteTxt());

  constructor() {
    effect(() => {
      sessionStorage.setItem(SS.ADD_TASK_BAR_TXT, this.inputTxt());
    });
    effect(() => {
      sessionStorage.setItem(SS.ADD_TASK_BAR_NOTE, this.noteTxt());
    });
  }

  updateProjectId(projectId: string): void {
    this._taskInputState.update((state) => ({ ...state, projectId }));
    // Clear auto-detected flag when manually changing project
    this.isAutoDetected.set(false);
  }

  updateDate(date: string | null, time?: string | null): void {
    this._taskInputState.update((state) => ({
      ...state,
      date,
      time: time !== undefined ? this._normTime(time) : state.time,
      isDateExplicitlyCleared: date ? false : state.isDateExplicitlyCleared,
    }));
  }

  updateTime(time: string | null): void {
    this._taskInputState.update((state) => ({ ...state, time: this._normTime(time) }));
  }

  updateSpent(spent: TimeSpentOnDay | null): void {
    this._taskInputState.update((state) => ({ ...state, spent }));
  }

  updateRemindOption(remindOption: TaskReminderOptionId | null): void {
    this._taskInputState.update((state) => ({ ...state, remindOption }));
  }

  updateEstimate(estimate: number | null): void {
    this._taskInputState.update((state) => ({ ...state, estimate }));
  }

  toggleTag(tag: Tag, cleanedInputTxt?: string): void {
    this._taskInputState.update((state) => {
      const isSelected = state.tagIds.includes(tag.id);
      return {
        ...state,
        tagIds: isSelected
          ? state.tagIds.filter((id) => id !== tag.id)
          : [...state.tagIds, tag.id],
      };
    });
    if (cleanedInputTxt !== undefined) {
      this.inputTxt.set(cleanedInputTxt);
    }
  }

  updateTagIds(tagIds: string[]): void {
    this._taskInputState.update((state) => ({ ...state, tagIds }));
  }

  updateTagIdsFromTxt(tagIdsFromTxt: string[]): void {
    this._taskInputState.update((state) => ({ ...state, tagIdsFromTxt }));
  }

  updateNewTagTitles(newTagTitles: string[]): void {
    this._taskInputState.update((state) => ({ ...state, newTagTitles }));
  }

  updateCleanText(cleanText: string | null): void {
    this._taskInputState.update((state) => ({ ...state, cleanText }));
  }

  updateInputTxt(inputTxt: string): void {
    this.inputTxt.set(inputTxt);
  }

  clearTags(cleanedInputTxt?: string): void {
    this._taskInputState.update((state) => ({
      ...state,
      tagIds: [],
      tagIdsFromTxt: [],
      newTagTitles: [],
    }));
    if (cleanedInputTxt !== undefined) {
      this.inputTxt.set(cleanedInputTxt);
    }
  }

  clearDate(cleanedInputTxt?: string): void {
    this._taskInputState.update((state) => ({
      ...state,
      date: null,
      time: null,
      isDateExplicitlyCleared: true,
    }));
    if (cleanedInputTxt !== undefined) {
      this.inputTxt.set(cleanedInputTxt);
    }
  }

  clearEstimate(cleanedInputTxt?: string): void {
    this._taskInputState.update((state) => ({ ...state, estimate: null }));
    if (cleanedInputTxt !== undefined) {
      this.inputTxt.set(cleanedInputTxt);
    }
  }

  updateAttachments(attachments: TaskAttachment[]): void {
    this._taskInputState.update((state) => ({ ...state, attachments }));
  }

  clearAttachments(cleanedInputTxt?: string): void {
    this._taskInputState.update((state) => ({ ...state, attachments: [] }));
    if (cleanedInputTxt !== undefined) {
      this.inputTxt.set(cleanedInputTxt);
    }
  }

  updateRepeatSetting(repeatQuickSetting: RepeatQuickSetting): void {
    this._taskInputState.update((state) => ({ ...state, repeatQuickSetting }));
  }

  clearRepeatSetting(): void {
    this._taskInputState.update((state) => ({ ...state, repeatQuickSetting: null }));
  }

  updateDeadline(deadlineDate: string | null, deadlineTime?: string | null): void {
    this._taskInputState.update((state) => ({
      ...state,
      deadlineDate,
      deadlineTime:
        deadlineTime !== undefined ? this._normTime(deadlineTime) : state.deadlineTime,
    }));
  }

  // Recover a stray seconds component (e.g. a pasted `13:30:00`) so a time the
  // user intended survives validation at task creation instead of being dropped
  // to a date-only due/deadline (#7802). Empty/null values pass through.
  private _normTime(time: string | null): string | null {
    return time ? normalizeClockStr(time) : time;
  }

  updateDeadlineRemindOption(deadlineRemindOption: TaskReminderOptionId | null): void {
    this._taskInputState.update((state) => ({ ...state, deadlineRemindOption }));
  }

  clearDeadline(cleanedInputTxt?: string): void {
    this._taskInputState.update((state) => ({
      ...state,
      deadlineDate: null,
      deadlineTime: null,
      deadlineRemindOption: null,
    }));
    if (cleanedInputTxt !== undefined) {
      this.inputTxt.set(cleanedInputTxt);
    }
  }

  resetAfterAdd(): void {
    // Only clear input text and tags, preserve project, date, and estimate
    this._taskInputState.update((state) => ({
      ...state,
      tagIds: [],
      tagIdsFromTxt: [],
      newTagTitles: [],
      cleanText: null,
      attachments: [],
      repeatQuickSetting: null,
      deadlineDate: null,
      deadlineTime: null,
      deadlineRemindOption: null,
    }));
    this.inputTxt.set('');
    // Clear the note text but keep the panel expanded so consecutive
    // note-tasks stay convenient (mirrors how project/date are preserved).
    this.noteTxt.set('');
    // Keep isAutoDetected as is to preserve project selection
  }

  setAutoDetectedProjectId(projectId: string): void {
    this.updateProjectId(projectId);
    this.isAutoDetected.set(true);
  }
}
