/**
 * TypeScript Task Manager Module
 *
 * Demonstrates interfaces, enums, generics, and type-safe patterns.
 */

export enum TaskStatus {
    Pending = 'pending',
    InProgress = 'in_progress',
    Completed = 'completed',
    Cancelled = 'cancelled',
}

export interface Task {
    readonly id: number;
    title: string;
    description: string;
    status: TaskStatus;
    priority: number;
    tags: ReadonlyArray<string>;
    createdAt: Date;
    completedAt: Date | null;
}

export interface TaskFilter {
    status?: TaskStatus;
    priority?: number;
    tags?: string[];
    search?: string;
}

type TaskInput = Pick<Task, 'title'> & Partial<Omit<Task, 'id' | 'createdAt' | 'completedAt'>>;

/**
 * A generic repository that stores items keyed by a numeric ID.
 */
function createRepository<T extends { id: number }>() {
    const items = new Map<number, T>();

    return {
        get: (id: number): T | undefined => items.get(id),
        set: (item: T): void => { items.set(item.id, item); },
        delete: (id: number): boolean => items.delete(id),
        values: (): T[] => [...items.values()],
        size: (): number => items.size,
        clear: (): void => items.clear(),
    };
}

let autoId = 0;

export class TaskManager {
    private repo = createRepository<Task>();
    private listeners: Array<(event: string, task: Task) => void> = [];

    constructor(initial: TaskInput[] = []) {
        for (const input of initial) {
            this.add(input);
        }
    }

    add(input: TaskInput): Task {
        const task: Task = {
            id: ++autoId,
            title: input.title,
            description: input.description ?? '',
            status: input.status ?? TaskStatus.Pending,
            priority: input.priority ?? 0,
            tags: Object.freeze([...(input.tags ?? [])]),
            createdAt: new Date(),
            completedAt: null,
        };
        this.repo.set(task);
        this.notify('added', task);
        return task;
    }

    complete(id: number): Task | null {
        const task = this.repo.get(id);
        if (!task) return null;

        const updated: Task = {
            ...task,
            status: TaskStatus.Completed,
            completedAt: new Date(),
        };
        this.repo.set(updated);
        this.notify('completed', updated);
        return updated;
    }

    remove(id: number): boolean {
        const task = this.repo.get(id);
        if (!task) return false;
        this.repo.delete(id);
        this.notify('removed', task);
        return true;
    }

    filter(criteria: TaskFilter): Task[] {
        return this.repo.values().filter((task) => {
            if (criteria.status !== undefined && task.status !== criteria.status) return false;
            if (criteria.priority !== undefined && task.priority < criteria.priority) return false;
            if (criteria.tags?.length && !criteria.tags.some((t) => task.tags.includes(t))) return false;
            if (criteria.search) {
                const q = criteria.search.toLowerCase();
                if (!task.title.toLowerCase().includes(q) && !task.description.toLowerCase().includes(q)) {
                    return false;
                }
            }
            return true;
        });
    }

    stats(): { total: number; byStatus: Record<TaskStatus, number> } {
        const byStatus = {
            [TaskStatus.Pending]: 0,
            [TaskStatus.InProgress]: 0,
            [TaskStatus.Completed]: 0,
            [TaskStatus.Cancelled]: 0,
        };
        for (const task of this.repo.values()) {
            byStatus[task.status]++;
        }
        return { total: this.repo.size(), byStatus };
    }

    onChange(listener: (event: string, task: Task) => void): () => void {
        this.listeners.push(listener);
        return () => {
            this.listeners = this.listeners.filter((fn) => fn !== listener);
        };
    }

    private notify(event: string, task: Task): void {
        for (const fn of this.listeners) {
            fn(event, { ...task });
        }
    }
}

export default TaskManager;