using System.ComponentModel;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Ui.Services;

public interface IWorkerClient : INotifyPropertyChanged
{
    bool IsConnected { get; }
    bool IsReconnecting { get; }

    event Action<string, string, DateTime>? TaskStartedEvent;
    event Action<string, string, string, DateTime>? TaskFinishedEvent;
    event Action<string>? TaskUpdatedEvent;
    /// <summary>Raised once when the SignalR connection is first established, and again on every reconnect.</summary>
    event Action? ConnectionRestoredEvent;
    event Action<string>? WorktreeUpdatedEvent;
    event Action<string>? ListUpdatedEvent;
    event Action<string, string>? TaskMessageEvent;
    event Action<WorkerLogEntry>? WorkerLogReceivedEvent;

    /// <summary>A running task raised a question via AskUser: (taskId, questionId, question).</summary>
    event Action<string, string, string>? TaskQuestionAskedEvent;
    /// <summary>A pending question was answered, timed out, or the run ended: (taskId, questionId).</summary>
    event Action<string, string>? TaskQuestionResolvedEvent;

    /// <summary>A running list-handler session called handoff_list_handler at the end of a phase:
    /// (handlerTaskId, survivingTaskIds, nextPhase). The UI opens a second ConPTY tile for the same
    /// task to carry out nextPhase ("wait" | "merge" | "wait_final" | "merge_final").</summary>
    event Action<string, IReadOnlyList<string>, string>? HandoffRequestedEvent;

    /// <summary>An agent added a note through the MCP tool — an open notes editor reloads.</summary>
    event Action? NotesUpdatedEvent;

    event Action? PrepStartedEvent;
    event Action<string>? PrepLineEvent;
    event Action<bool>? PrepFinishedEvent;

    /// <summary>(opKey, phase, current, total) — generic progress channel for long-running worker
    /// operations (merge phases, worktree cleanup, startup recovery, planning integration, ...).
    /// opKey is the TaskId for a task-bound operation, otherwise a stable string. current/total
    /// is meant to be shown as text, never a progress bar — most operations have no meaningful
    /// total.</summary>
    event Action<string, string, int, int>? OperationProgressEvent;

    /// <summary>(taskId, phase, elapsedSeconds) — phase of an in-flight single-task merge
    /// ("merging" | "verifying"). Fires while the MergeTask call itself is still pending, so the
    /// waiting UI can show what it's blocked on; the verify phase re-fires every 30 s. A thin
    /// forwarder over <see cref="OperationProgressEvent"/> kept for existing consumers (elapsed
    /// seconds riding in the generic "current" slot).</summary>
    event Action<string, string, int>? MergeProgressEvent;

    event Action<string, string>? PlanningMergeStartedEvent;
    event Action<string, string>? PlanningSubtaskMergedEvent;
    /// <summary>(planningTaskId, subtaskId, conflictedFiles, externallyDriven). externallyDriven
    /// is true when an MCP session (not the UI) started the unit merge — the resolver must not
    /// auto-open in that case.</summary>
    event Action<string, string, IReadOnlyList<string>, bool>? PlanningMergeConflictEvent;
    /// <summary>(planningTaskId, reason). reason is set when the merge stopped on a real failure
    /// (blocked/verify_failed/untracked_collision) rather than a deliberate abort.</summary>
    event Action<string, string?>? PlanningMergeAbortedEvent;
    event Action<string>? PlanningCompletedEvent;

    event Action<PrimeFiredEvent>? PrimeFired;

    string? LastApproveTarget { get; }

    IReadOnlyList<ActiveTaskDto> GetActiveTasks();

    Task WakeQueueAsync();
    Task RunNowAsync(string taskId);
    Task ContinueTaskAsync(string taskId, string followUpPrompt);
    /// <summary>Answer a question a running task raised via AskUser.</summary>
    Task AnswerTaskQuestionAsync(string taskId, string questionId, string answer);
    /// <summary>The question a running task is currently blocked on, if any (for re-attach).</summary>
    Task<PendingQuestionDto?> GetPendingQuestionAsync(string taskId);
    Task ResetTaskAsync(string taskId);
    Task CancelTaskAsync(string taskId);
    /// <summary>Deletes a task via the worker (mirrors the MCP delete_task tool), so a deleted
    /// child correctly advances a WaitingForChildren parent. Returns (false, message) instead of
    /// throwing when the task has children or is running, preserving the FK-error UX.</summary>
    Task<(bool Ok, string? Error)> DeleteTaskAsync(string taskId);
    Task<List<AgentInfo>> GetAgentsAsync();
    Task RefreshAgentsAsync();
    Task<SeedResultDto?> RestoreDefaultAgentsAsync();
    Task<ListConfigDto?> GetListConfigAsync(string listId);
    Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto);
    /// <summary>Repo-import folders remembered for the "Add repos as lists" dialog. Throws on
    /// a failed hub call — the caller surfaces the failure rather than showing an empty list.</summary>
    Task<List<string>> GetRepoImportFoldersAsync();
    /// <summary>Persists the repo-import folder list. Throws on a failed hub call.</summary>
    Task SetRepoImportFoldersAsync(List<string> folders);
    Task<List<SessionSkillDto>> GetSessionSkillsAsync();
    Task<List<string>> InstallSessionSkillAsync(string url);
    Task UpdateSessionSkillAsync(string sourceUrl);
    Task RemoveSessionSkillAsync(string sourceUrl);
    /// <summary>Returns the base-dirty heads-up when this transition just queued the task against
    /// a list whose working dir has uncommitted changes (null otherwise) — a new worktree forks
    /// from the last commit, not the working tree, so those changes won't be included.</summary>
    Task<BaseDirtyWarningDto?> SetTaskStatusAsync(string taskId, TaskStatus status);
    /// <summary>Guarded "manual done toggle" (checks the task is currently Idle server-side
    /// before flipping it to Done) — replaces a raw EF write in the task-list checkbox path.</summary>
    Task SetTaskDoneAsync(string taskId);
    /// <summary>Guarded un-toggle (checks the task is currently Done server-side before
    /// flipping it back to Idle).</summary>
    Task UnsetTaskDoneAsync(string taskId);
    /// <summary>Guarded "remove from queue" (checks the task is currently Queued server-side
    /// and clears BlockedByTaskId in the same update) — call once per row (parent and each
    /// queued child) rather than cascading server-side.</summary>
    Task DequeueTaskAsync(string taskId);
    Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch);
    Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch);
    Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage);
    Task RejectReviewToQueueAsync(string taskId, string feedback);
    Task RejectReviewToIdleAsync(string taskId);
    Task CancelReviewAsync(string taskId);

    // ── Conflict resolution (worker hub side implemented by Layer C) ──
    Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch);
    Task<MergeConflictDocumentsDto> GetMergeConflictDocumentsAsync(string taskId);
    Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent);
    Task<MergeResultDto> ContinueConflictMergeAsync(string taskId);
    Task AbortConflictMergeAsync(string taskId);
    Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default);
    /// <summary>Commits an interactively-worked task's worktree and moves it to WaitingForReview
    /// (the only path that flips a hand-driven ConPTY session into the review/merge pipeline).</summary>
    Task SubmitTaskForReviewAsync(string taskId, CancellationToken ct = default);
    /// <summary>Launch spec for an embedded ConPTY terminal to open an interactive session
    /// in a task's worktree (same worktree prep as an autonomous run).</summary>
    Task<LaunchSpec> GetInteractiveLaunchSpecAsync(string taskId, CancellationToken ct = default);
    /// <summary>Launch spec for an ad-hoc interactive session in an arbitrary directory --
    /// no task, no worktree.</summary>
    Task<LaunchSpec> GetAdHocLaunchSpecAsync(string directory, CancellationToken ct = default);
    /// <summary>Launch spec for an embedded ConPTY "merge helper" session that drives the given
    /// tasks to a merged/Done state. listId scopes the session (and cwd) to that list.
    /// handlerTaskId (from CreateMergeHelperTaskAsync) is rendered into the brief so the session
    /// can call handoff_list_handler on its own handler task.</summary>
    Task<LaunchSpec> GetMergeHelperLaunchSpecAsync(IReadOnlyList<string> taskIds, string listId, string handlerTaskId, CancellationToken ct = default);
    /// <summary>Creates the ClaudeDo task that owns a list-handler run (one per run, Idle/IsManual,
    /// never queued) so the ConPTY tile can be task-based instead of ad-hoc. Returns the new task id.</summary>
    Task<string> CreateMergeHelperTaskAsync(
        IReadOnlyList<string> taskIds, string listId, string title, string descriptionHeader, CancellationToken ct = default);
    /// <summary>Launch spec for the fresh ConPTY session a merge-helper run hands off to once a phase
    /// is done -- reuses the SAME handler task id (no new task created). nextPhase picks the next
    /// session's role: "wait" | "merge" | "wait_final" | "merge_final".</summary>
    Task<LaunchSpec> GetMergeHelperHandoffLaunchSpecAsync(
        string taskId, IReadOnlyList<string> survivingTaskIds, string nextPhase, CancellationToken ct = default);
    /// <summary>Starts a planning session and returns the launch spec for an embedded ConPTY
    /// planning terminal (replaces StartPlanningSessionAsync's external wt window).</summary>
    Task<LaunchSpec> GetPlanningStartLaunchSpecAsync(string taskId, CancellationToken ct = default);
    /// <summary>Resumes a planning session and returns its embedded-ConPTY launch spec.</summary>
    Task<LaunchSpec> GetPlanningResumeLaunchSpecAsync(string taskId, CancellationToken ct = default);
    Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default);
    Task<DiscardPlanningOutcome> DiscardPlanningSessionAsync(string taskId, bool dequeueQueuedChildren = false, CancellationToken ct = default);
    Task FinalizePlanningSessionAsync(string taskId, bool queueAgentTasks = true, CancellationToken ct = default);
    Task<int> GetPendingDraftCountAsync(string taskId, CancellationToken ct = default);
    Task<MergeTargetsDto?> GetMergeTargetsAsync(string taskId);
    Task<IReadOnlyList<SubtaskDiffDto>> GetPlanningAggregateAsync(string planningTaskId);
    Task<CombinedDiffResultDto?> BuildPlanningIntegrationBranchAsync(string planningTaskId, string targetBranch);
    Task ContinuePlanningMergeAsync(string planningTaskId);
    Task AbortPlanningMergeAsync(string planningTaskId);
    /// <summary>Unit merges currently paused on a conflict that an MCP session started. Called
    /// on (re)connect to recover the "don't auto-open" banner state after a UI restart.</summary>
    Task<IReadOnlyList<PlanningMergeConflictStateDto>> GetActiveExternalPlanningMergeConflictsAsync();
    Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default);
    Task<string?> GetWeekReportAsync(DateOnly start, DateOnly end);
    Task<string> GenerateWeekReportAsync(DateOnly start, DateOnly end);
    Task<bool> RunDailyPrepNowAsync();
    Task RefineTaskAsync(string taskId);

    event Action<string>? RefineStartedEvent;
    event Action<string, bool, string?>? RefineFinishedEvent;
    Task ClearMyDayAsync();
    Task<AppSettingsDto?> GetAppSettingsAsync();
    Task UpdateAppSettingsAsync(AppSettingsDto dto);
    Task<List<DailyNoteDto>> GetDailyNotesAsync(DateOnly day);
    /// <summary>Both ends inclusive.</summary>
    Task<List<DailyNoteDto>> GetDailyNotesBetweenAsync(DateOnly start, DateOnly end);
    Task<DailyNoteDto?> AddDailyNoteAsync(DateOnly day, string text);
    Task UpdateDailyNoteAsync(string id, string text);
    Task DeleteDailyNoteAsync(string id);
    Task<string> GetLastPrepLogAsync();
    Task<IReadOnlyList<WorkerLogEntry>> GetRecentLogsAsync();

    /// <summary>Git SHA the running worker was built from (null when offline or the build isn't stamped).</summary>
    Task<WorkerBuildInfoDto?> GetWorkerBuildInfoAsync();

    Task<List<PrimeScheduleDto>> GetPrimeSchedulesAsync();
    Task<PrimeScheduleDto?> UpsertPrimeScheduleAsync(PrimeScheduleDto dto);
    Task DeletePrimeScheduleAsync(Guid id);

    Task UpdateListAsync(UpdateListDto dto);
    Task UpdateListConfigAsync(UpdateListConfigDto dto);

    Task<WorktreeCleanupDto?> CleanupFinishedWorktreesAsync(string? listId = null);
    Task<WorktreeResetDto?> ResetAllWorktreesAsync();
    Task<List<WorktreeOverviewDto>> GetWorktreesOverviewAsync(string? listId);
    Task<(bool Ok, string? Error)> SetWorktreeStateAsync(string taskId, WorktreeState newState);
    Task<ForceRemoveResultDto?> ForceRemoveWorktreeAsync(string taskId);

    Task<ClaudeBinDto?> GetClaudeBinAsync();
    Task<ClaudeBinDto?> SetClaudeBinAsync(string? value);

    Task<OnlineInboxStateDto?> GetOnlineInboxStateAsync();
    Task SetOnlineInboxConfigAsync(OnlineInboxConfigInputDto input);
    Task SetOnlineInboxAuthAsync(string refreshToken);
    Task ClearOnlineInboxAuthAsync();

    /// <summary>Raised whenever the worker's usage poller ticks (success or failure).</summary>
    event Action<UsageSnapshotDto>? UsageUpdatedEvent;
    Task<UsageSnapshotDto?> GetUsageSnapshotAsync();

    /// <summary>Forces an out-of-band usage poll on the worker and returns the fresh snapshot.</summary>
    Task<UsageSnapshotDto?> RefreshUsageAsync();
    Task<IReadOnlyList<ModelUsageRowDto>> GetModelUsageAsync(DateOnly from, DateOnly to);
    Task<IReadOnlyList<TaskUsageRowDto>> GetTaskUsageAsync(DateOnly from, DateOnly to);
    Task<TokenTrackerStatusDto?> GetTokenTrackerStatusAsync();
    Task<TokenTrackerStatusDto?> RefreshTokenTrackerAsync();
    Task<TokenTrackerStatusDto?> InstallTokenTrackerAsync();

    /// <summary>Starts TokenTracker's local dashboard on the worker and returns its URL; opening
    /// the browser is the caller's job.</summary>
    Task<TokenTrackerDashboardDto?> OpenTokenTrackerDashboardAsync();

    Task<TicketSettingsDto?> GetTicketSettingsAsync();
    Task SetTicketApiBaseUrlAsync(string? baseUrl);
    Task SetTicketTokenAsync(string token);
    Task ClearTicketTokenAsync();
    Task<TicketConnectionDto?> TestTicketConnectionAsync();
    Task<List<TicketProjectDto>> GetTicketProjectsAsync();
    /// <summary>Imports open tickets assigned to the token owner into the given list. Throws
    /// HubException (with a user-readable message) on failure so the caller can surface it via
    /// the footer error strip.</summary>
    Task<TicketImportResultDto> ImportTicketsAsync(string listId);
}
