/**
 * Subtask operations for tickets.
 */
import { randomUUID } from 'node:crypto';
import { PMO_TABLES } from '../schema.js';
import { PMOError } from '../types.js';
import { slugify } from '../utils.js';
import { wrapSqliteError } from './helpers.js';
const T = PMO_TABLES;
export class SubtaskStorage {
    ctx;
    constructor(ctx) {
        this.ctx = ctx;
    }
    /**
     * Add a subtask to a ticket.
     */
    async addSubtask(ticketId, title) {
        // Verify ticket exists and get project_id
        const ticket = this.ctx.db.prepare(`
      SELECT id, project_id FROM ${T.tickets} WHERE id = ?
    `).get(ticketId);
        if (!ticket) {
            throw new PMOError('NOT_FOUND', `Ticket not found: ${ticketId}`, ticketId);
        }
        // Get current max position
        const maxPos = this.ctx.db.prepare(`
      SELECT COALESCE(MAX(position), -1) as max_pos
      FROM ${T.subtasks} WHERE ticket_id = ?
    `).get(ticketId);
        // Generate unique ID - start with slugified title, append counter if collision
        const baseId = slugify(title);
        let id = baseId;
        let counter = 1;
        // Check for existing subtask with same ID and append counter if needed
        while (true) {
            const existing = this.ctx.db.prepare(`
        SELECT 1 FROM ${T.subtasks} WHERE ticket_id = ? AND id = ?
      `).get(ticketId, id);
            if (!existing)
                break;
            counter++;
            id = `${baseId}-${counter}`;
        }
        const position = maxPos.max_pos + 1;
        try {
            this.ctx.db.prepare(`
        INSERT INTO ${T.subtasks} (id, ticket_id, title, done, position)
        VALUES (?, ?, ?, 0, ?)
      `).run(id, ticketId, title, position);
        }
        catch (err) {
            wrapSqliteError('Subtask', 'create', err);
        }
        // Update ticket timestamp
        this.ctx.db.prepare(`
      UPDATE ${T.tickets} SET updated_at = ? WHERE id = ?
    `).run(Date.now(), ticketId);
        this.ctx.updateBoardTimestamp(ticket.project_id);
        return {
            id,
            title,
            done: false,
        };
    }
    /**
     * Toggle a subtask's done status.
     */
    async toggleSubtask(ticketId, subtaskId) {
        // Get ticket's project_id for board timestamp update
        const ticket = this.ctx.db.prepare(`
      SELECT project_id FROM ${T.tickets} WHERE id = ?
    `).get(ticketId);
        const existing = this.ctx.db.prepare(`
      SELECT * FROM ${T.subtasks}
      WHERE ticket_id = ? AND id = ?
    `).get(ticketId, subtaskId);
        if (!existing) {
            throw new PMOError('NOT_FOUND', `Subtask not found: ${subtaskId}`);
        }
        const newDone = existing.done === 0 ? 1 : 0;
        this.ctx.db.prepare(`
      UPDATE ${T.subtasks}
      SET done = ?
      WHERE ticket_id = ? AND id = ?
    `).run(newDone, ticketId, subtaskId);
        // Update ticket timestamp
        this.ctx.db.prepare(`
      UPDATE ${T.tickets} SET updated_at = ? WHERE id = ?
    `).run(Date.now(), ticketId);
        if (ticket) {
            this.ctx.updateBoardTimestamp(ticket.project_id);
        }
        return {
            id: existing.id,
            title: existing.title,
            done: newDone === 1,
        };
    }
    /**
     * Remove a subtask from a ticket.
     */
    async removeSubtask(ticketId, subtaskId) {
        // Get ticket's project_id for board timestamp update
        const ticket = this.ctx.db.prepare(`
      SELECT project_id FROM ${T.tickets} WHERE id = ?
    `).get(ticketId);
        const result = this.ctx.db.prepare(`
      DELETE FROM ${T.subtasks}
      WHERE ticket_id = ? AND id = ?
    `).run(ticketId, subtaskId);
        if (result.changes === 0) {
            throw new PMOError('NOT_FOUND', `Subtask not found: ${subtaskId}`);
        }
        // Update ticket timestamp
        this.ctx.db.prepare(`
      UPDATE ${T.tickets} SET updated_at = ? WHERE id = ?
    `).run(Date.now(), ticketId);
        if (ticket) {
            this.ctx.updateBoardTimestamp(ticket.project_id);
        }
    }
}
export class AcceptanceCriteriaStorage {
    ctx;
    constructor(ctx) {
        this.ctx = ctx;
    }
    /**
     * Add an acceptance criterion to a ticket.
     */
    async addAcceptanceCriterion(ticketId, criterion) {
        // Verify ticket exists and get project_id
        const ticket = this.ctx.db.prepare(`
      SELECT id, project_id FROM ${T.tickets} WHERE id = ?
    `).get(ticketId);
        if (!ticket) {
            throw new PMOError('NOT_FOUND', `Ticket not found: ${ticketId}`, ticketId);
        }
        // Get current max position
        const maxPos = this.ctx.db.prepare(`
      SELECT COALESCE(MAX(position), -1) as max_pos
      FROM ${T.ticket_acceptance_criteria} WHERE ticket_id = ?
    `).get(ticketId);
        // Use UUID to guarantee uniqueness even when multiple ACs are added in the same millisecond
        const id = `ac-${randomUUID()}`;
        const position = maxPos.max_pos + 1;
        try {
            this.ctx.db.prepare(`
        INSERT INTO ${T.ticket_acceptance_criteria} (id, ticket_id, criterion, verifiable, verified, position)
        VALUES (?, ?, ?, 1, 0, ?)
      `).run(id, ticketId, criterion, position);
        }
        catch (err) {
            wrapSqliteError('Acceptance criterion', 'create', err);
        }
        this.ctx.db.prepare(`
      UPDATE ${T.tickets} SET updated_at = ? WHERE id = ?
    `).run(Date.now(), ticketId);
        this.ctx.updateBoardTimestamp(ticket.project_id);
        return {
            id,
            ticketId,
            criterion,
            verifiable: true,
            verified: false,
            position,
        };
    }
    /**
     * Remove an acceptance criterion from a ticket.
     */
    async removeAcceptanceCriterion(ticketId, criterionId) {
        // Get ticket's project_id for board timestamp update
        const ticket = this.ctx.db.prepare(`
      SELECT project_id FROM ${T.tickets} WHERE id = ?
    `).get(ticketId);
        const result = this.ctx.db.prepare(`
      DELETE FROM ${T.ticket_acceptance_criteria}
      WHERE ticket_id = ? AND id = ?
    `).run(ticketId, criterionId);
        if (result.changes === 0) {
            throw new PMOError('NOT_FOUND', `Acceptance criterion not found: ${criterionId}`);
        }
        this.ctx.db.prepare(`
      UPDATE ${T.tickets} SET updated_at = ? WHERE id = ?
    `).run(Date.now(), ticketId);
        if (ticket) {
            this.ctx.updateBoardTimestamp(ticket.project_id);
        }
    }
    /**
     * Clear all acceptance criteria from a ticket.
     */
    async clearAcceptanceCriteria(ticketId) {
        // Get ticket's project_id for board timestamp update
        const ticket = this.ctx.db.prepare(`
      SELECT project_id FROM ${T.tickets} WHERE id = ?
    `).get(ticketId);
        this.ctx.db.prepare(`
      DELETE FROM ${T.ticket_acceptance_criteria} WHERE ticket_id = ?
    `).run(ticketId);
        this.ctx.db.prepare(`
      UPDATE ${T.tickets} SET updated_at = ? WHERE id = ?
    `).run(Date.now(), ticketId);
        if (ticket) {
            this.ctx.updateBoardTimestamp(ticket.project_id);
        }
    }
}
//# sourceMappingURL=subtasks.js.map