import { TagRepository } from "../repositories/TagRepository.js";
import { TagDtoEntityMapper } from "../mappers/TagDtoEntityMapper.js";
import { TagDto } from "../api/dtos/TagDto.js";

/**
 * Handles business logic for tag operations.
 * Called by TagRestResource; keeps routing and business logic separated.
 */
export class TagHandler {
    private readonly tagRepository: TagRepository;
    private readonly tagMapper: TagDtoEntityMapper;

    constructor() {
        this.tagRepository = new TagRepository();
        this.tagMapper = new TagDtoEntityMapper();
    }

    /**
     * Returns all tags.
     */
    async getAll(): Promise<TagDto[]> {
        const entities = await this.tagRepository.findAll();
        return entities.map(e => this.tagMapper.toDto(e));
    }

    /**
     * Creates a new tag or updates an existing one.
     *
     * - If a tag with the same description already exists (case-insensitive)
     *   the existing tag is returned unchanged (idempotent behaviour matching
     *   the "create-or-update" contract).
     * - If a DTO with an explicit ID is supplied, the description of that tag
     *   is updated.
     * - Otherwise a brand-new tag is created.
     */
    async createOrUpdate(dto: TagDto): Promise<TagDto> {
        // Update path: ID supplied — find and merge changes into existing entity
        if (dto.id) {
            const existing = await this.tagRepository.findById(dto.id);
            if (!existing) {
                throw new Error(`Tag with id ${dto.id} not found`);
            }
            const updated = await this.tagRepository.update(
                this.tagMapper.mergeDtoIntoEntity(dto, existing)
            );
            return this.tagMapper.toDto(updated);
        }

        // Create path: check for duplicate description first
        const duplicate = await this.tagRepository.findByDescription(dto.description);
        if (duplicate) {
            return this.tagMapper.toDto(duplicate);
        }

        const created = await this.tagRepository.create(this.tagMapper.toEntity(dto));
        return this.tagMapper.toDto(created);
    }

    /**
     * Deletes a tag by ID.
     * The ON DELETE CASCADE on the recipe_tag join table ensures all
     * references are cleaned up automatically by the database.
     *
     * @throws Error when the tag does not exist.
     */
    async delete(id: string): Promise<void> {
        const existing = await this.tagRepository.findById(id);
        if (!existing) {
            throw new Error(`Tag with id ${id} not found`);
        }
        await this.tagRepository.delete(id);
    }
}