import { promisify } from 'node:util';
import { execFile } from 'node:child_process';
import { IS_WINDOWS } from '../../platform.js';
import { AgentValidatorService } from '../../services/agents/common/agent-validator.service.js';
import { VersionService } from '../../services/version.service.js';
import { WorktreeService } from '../../services/git/worktree.service.js';
import { GitCommitService } from '../../services/git/git-commit.service.js';
import { FileSystemService } from '../../services/file-system.service.js';
import { ApplicationBriefStore } from '../../services/filesystem/application-brief.store.js';
import { FsProjectScaffoldService } from '../../services/project-scaffold/fs-project-scaffold.service.js';
import { BunShadcnScaffolder } from '../../services/scaffolding/bun-shadcn-scaffolder.service.js';
import { LocalAttachmentStorageService } from '../../services/storage/local-attachment-storage.service.js';
import { NoopWebhookService } from '../../services/integrations/noop-webhook.service.js';
import nodePath from 'node:path';
import { ApplicationCreationPromptBuilder } from '../../services/agents/application-creation/application-creation-prompt.builder.js';
import { PlatformAgentAuthDetectorService } from '../../services/agent-auth-detector/platform-agent-auth-detector.service.js';
import { ToolInstallerServiceImpl } from '../../services/tool-installer/tool-installer.service.js';
import { GitPrService } from '../../services/git/git-pr.service.js';
import { GitForkService } from '../../services/git/git-fork.service.js';
import { SkillInjectorService } from '../../services/skill-injector.service.js';
import { BedrockIntegrationService } from '../../services/integrations/bedrock-integration.service.js';
import { FileSystemBedrockMemoryReader } from '../../services/integrations/file-system-bedrock-memory-reader.service.js';
import { ClaudeSettingsReconciler } from '../../services/filesystem/claude-settings-reconciler.service.js';
import { IBedrockIntegrationServiceToken, IBedrockMemoryReaderToken, IClaudeSettingsReconcilerToken, } from '../tokens.js';
import { GitHubRepositoryService } from '../../services/external/github-repository.service.js';
import { BrowserOpenerService } from '../../services/browser-opener.service.js';
import { PtyTerminalSessionService } from '../../services/terminal/pty-terminal-session.service.js';
import { NodeApplicationFileSystemService } from '../../services/filesystem/node-application-file-system.service.js';
import { AttachmentStorageService } from '../../services/attachment-storage.service.js';
import { ShepInstanceService } from '../../services/shep-instance.service.js';
import { DesktopNotifier } from '../../services/notifications/desktop-notifier.js';
import { NotificationService } from '../../services/notifications/notification.service.js';
import { getNotificationBus } from '../../services/notifications/notification-bus.js';
import { ConflictResolutionService } from '../../services/agents/conflict-resolution/conflict-resolution.service.js';
import { ConsoleLogger } from '../../services/logging/console-logger.js';
import { OperationLogService } from '../../services/operation-log/operation-log.service.js';
import { ProcessLivenessAdapter } from '../../services/process/process-liveness.adapter.js';
import { NodeProjectBuildService } from '../../services/build/node-project-build.service.js';
import { InMemoryOperationLogEventBus } from '../../services/events/in-memory-operation-log-event-bus.js';
import { GitHubReviewService } from '../../services/code-review/github-review.service.js';
import { annotateFileDiffs } from '../../services/code-review/diff-annotation.service.js';
import { buildReviewPrompt } from '../../services/code-review/review-prompt-builder.service.js';
import { parseReviewOutput } from '../../services/code-review/review-output-parser.service.js';
import { SQLiteAgentMessageBus } from '../../services/agents/agent-message-bus/sqlite-agent-message-bus.js';
import { DeferredQuestionRegistry } from '../../services/agents/agent-question-service/deferred-question-registry.js';
import { GitHubIssueWriter } from '../../services/external/github-issue-writer.service.js';
import { GitHubIssueFetcher } from '../../services/external/github-issue.service.js';
import { AllContributorsWriter } from '../../services/contributors/all-contributors-writer.service.js';
import { SupervisorContributorActionGate } from '../../services/contributors/supervisor-contributor-action-gate.js';
import { DiscordOutreachPublisher } from '../../services/outreach/discord-outreach-publisher.service.js';
import { FileRecapPublisher } from '../../services/recap/file-recap-publisher.service.js';
import { DiscordRecapPublisher } from '../../services/recap/discord-recap-publisher.service.js';
import { GithubDiscussionRecapPublisher } from '../../services/recap/github-discussion-recap-publisher.service.js';
import { DiagnosticRunner } from '../../services/doctor/diagnostic-runner.service.js';
import { NodeVersionDiagnostic } from '../../../application/use-cases/doctor/diagnostics/node-version.diagnostic.js';
import { PnpmInstalledDiagnostic } from '../../../application/use-cases/doctor/diagnostics/pnpm-installed.diagnostic.js';
import { GitInstalledDiagnostic } from '../../../application/use-cases/doctor/diagnostics/git-installed.diagnostic.js';
import { GhCliAuthDiagnostic } from '../../../application/use-cases/doctor/diagnostics/gh-cli-auth.diagnostic.js';
import { AgentCliAvailabilityDiagnostic } from '../../../application/use-cases/doctor/diagnostics/agent-cli-availability.diagnostic.js';
import { DotenvPresenceDiagnostic } from '../../../application/use-cases/doctor/diagnostics/dotenv-presence.diagnostic.js';
import { WorkingTreeCleanDiagnostic } from '../../../application/use-cases/doctor/diagnostics/working-tree-clean.diagnostic.js';
import { MigrationStatusDiagnostic } from '../../../application/use-cases/doctor/diagnostics/migration-status.diagnostic.js';
import { TypespecFreshnessDiagnostic } from '../../../application/use-cases/doctor/diagnostics/typespec-freshness.diagnostic.js';
import { DiGraphValidationDiagnostic } from '../../../application/use-cases/doctor/diagnostics/di-graph-validation.diagnostic.js';
import { RecapChannel } from '../../../domain/generated/output.js';
/**
 * Register core infrastructure services: validators, filesystem, git, notifications,
 * logger, tool installer, attachment storage, shep-instance, browser opener, etc.
 *
 * Does NOT register services that need eager construction (e.g. deployment service)
 * or that live in topic-specific modules (agents, cloud deploy, ide/tools).
 */
export function registerServices(container) {
    // ExecFunction — on Windows, agent CLIs ship as .cmd/.ps1 scripts (e.g. cursor's
    // `agent.cmd`). execFile without shell: true cannot resolve .cmd extensions,
    // causing ENOENT. With shell: true, Node concatenates `file + ' ' + args.join(' ')`
    // and hands it to cmd.exe — which then re-tokenises on whitespace, splitting any
    // arg containing a space (e.g. `--description "snake game"` would arrive at gh
    // as two args). Quote args with whitespace or shell-special chars before joining.
    const execFileAsync = promisify(execFile);
    const quoteWindowsArg = (a) => {
        if (/^[A-Za-z0-9_./:=+@,-]+$/.test(a))
            return a;
        return `"${a.replace(/(["\\])/g, '\\$1')}"`;
    };
    const execFn = IS_WINDOWS
        ? (file, args, options) => execFileAsync(file, args.map(quoteWindowsArg), {
            ...options,
            shell: true,
            windowsHide: true,
        })
        : execFileAsync;
    container.registerInstance('ExecFunction', execFn);
    container.registerSingleton('IAgentValidator', AgentValidatorService);
    container.registerSingleton('IVersionService', VersionService);
    // IWebServerService is registered as a lazy proxy to avoid importing `next`
    // (~80ms) for non-web commands. The actual service is loaded on first method call.
    container.register('IWebServerService', {
        useFactory: () => {
            let instance = null;
            const getInstance = async () => {
                if (!instance) {
                    const { WebServerService } = await import('../../services/web-server.service.js');
                    instance = new WebServerService();
                }
                return instance;
            };
            return new Proxy({}, {
                get: (_target, prop) => {
                    return async (...args) => {
                        const svc = await getInstance();
                        // eslint-disable-next-line @typescript-eslint/no-explicit-any
                        return svc[prop](...args);
                    };
                },
            });
        },
    });
    container.registerSingleton('IWorktreeService', WorktreeService);
    container.registerSingleton('IGitCommitService', GitCommitService);
    container.registerSingleton('IFileSystemService', FileSystemService);
    container.registerSingleton('IApplicationBriefStore', ApplicationBriefStore);
    container.registerSingleton('IProjectScaffoldService', FsProjectScaffoldService);
    container.registerSingleton('IApplicationScaffolder', BunShadcnScaffolder);
    container.registerSingleton('IWebhookService', NoopWebhookService);
    container.register('IAttachmentStorage', {
        useFactory: (c) => {
            const db = c.resolve('Database');
            const dbFilename = db.name;
            const dbDir = dbFilename ? nodePath.dirname(dbFilename) : '.shep';
            const storagePath = nodePath.join(dbDir, 'attachments');
            return new LocalAttachmentStorageService(storagePath);
        },
    });
    container.registerSingleton('IApplicationCreationPromptBuilder', ApplicationCreationPromptBuilder);
    container.registerSingleton('IAgentAuthDetectorService', PlatformAgentAuthDetectorService);
    container.registerSingleton('ISkillInjectorService', SkillInjectorService);
    // ─── Bedrock integration (feature 098) ──────────────────────────────
    // Reconciler is registered first so the bedrock adapter (or any future
    // consumer) can resolve it without a forward-reference. Both are infra
    // services — singletons, string-token aliased through tokens.ts.
    container.registerSingleton(IClaudeSettingsReconcilerToken, ClaudeSettingsReconciler);
    container.registerSingleton(IBedrockIntegrationServiceToken, BedrockIntegrationService);
    container.registerSingleton(IBedrockMemoryReaderToken, FileSystemBedrockMemoryReader);
    container.registerSingleton('IToolInstallerService', ToolInstallerServiceImpl);
    container.registerSingleton('IGitPrService', GitPrService);
    container.registerSingleton('IGitForkService', GitForkService);
    container.registerSingleton('IGitHubRepositoryService', GitHubRepositoryService);
    container.registerSingleton('IApplicationFileSystemService', NodeApplicationFileSystemService);
    container.registerSingleton('ITerminalSessionService', PtyTerminalSessionService);
    container.registerSingleton(AttachmentStorageService);
    container.register('AttachmentStorageService', { useToken: AttachmentStorageService });
    container.register('IAttachmentStorageService', { useToken: AttachmentStorageService });
    container.registerSingleton('IShepInstanceService', ShepInstanceService);
    // Notification services
    const notificationBus = getNotificationBus();
    container.registerInstance('NotificationEventBus', notificationBus);
    container.register('DesktopNotifier', {
        useFactory: () => new DesktopNotifier(),
    });
    container.register('IDesktopNotifier', {
        useFactory: (c) => c.resolve('DesktopNotifier'),
    });
    container.register('INotificationService', {
        useFactory: (c) => {
            const bus = c.resolve('NotificationEventBus');
            const desktopNotif = c.resolve('DesktopNotifier');
            return new NotificationService(bus, desktopNotif);
        },
    });
    // Browser opener
    container.register('IBrowserOpener', {
        useFactory: () => new BrowserOpenerService(),
    });
    // Conflict resolution (shared by several feature use cases)
    container.registerSingleton('IConflictResolutionService', ConflictResolutionService);
    // Generic logger used by cloud deploy + other infrastructure consumers.
    container.registerSingleton('ILogger', ConsoleLogger);
    // Operation log service — orchestrating use cases append structured progress
    // entries here so the UI can render full operation history. Lazy-resolved so
    // the IOperationLogRepository (registered in register-repositories) is in
    // place by the time the first use case asks for the service.
    container.registerSingleton('IOperationLogService', OperationLogService);
    // Operation log event bus — SQLite repo publishes here after each
    // successful append; SSE route subscribes to fan out as notifications.
    container.registerSingleton('IOperationLogEventBus', InMemoryOperationLogEventBus);
    // Process liveness probe — hides `process.kill(pid, 0)` behind a port so
    // application + presentation layers never import `infrastructure/services/
    // process/is-process-alive` directly.
    container.registerSingleton('IProcessLivenessProbe', ProcessLivenessAdapter);
    container.registerSingleton('IProjectBuildService', NodeProjectBuildService);
    // ─── Code review (feature 090) services ─────────────────────────────
    container.registerSingleton('IPlatformReviewService', GitHubReviewService);
    container.registerInstance('DiffAnnotator', annotateFileDiffs);
    container.registerInstance('PromptBuilder', buildReviewPrompt);
    container.registerInstance('OutputParser', parseReviewOutput);
    // ─── Collaboration (feature 093) — agent message bus ────────────────
    // SQLite-backed bus reuses the shared shep.db so the bus is cross-process
    // by virtue of the WAL-mode database file. Singleton so subscriptions
    // share one poll loop per process (research decision 2).
    container.registerSingleton('IAgentMessageBus', SQLiteAgentMessageBus);
    // ─── Collaboration (feature 093) — deferred question registry ───────
    // In-process bridge between the SDK V2 canUseTool callback and the
    // DB-backed answer that may arrive from another process. Singleton so
    // every use case in this process shares one map of awaiters (task 16).
    container.registerSingleton('IDeferredQuestionRegistry', DeferredQuestionRegistry);
    // ─── Contributor onboarding (feature 097) — writers & publishers ────
    // Workspace root resolution mirrors `ShepInstanceService` so the file-based
    // adapters (all-contributors, file recap) write into the correct repo when
    // Shep runs against a checked-out target instance.
    const workspaceRoot = process.env.SHEP_INSTANCE_PATH ?? process.env.NEXT_PUBLIC_SHEP_INSTANCE_PATH ?? process.cwd();
    // GitHubIssueWriter takes two function-typed constructor params with
    // sensible defaults (see DiscordOutreachPublisher note below) — register
    // an instance so tsyringe never tries to reflect on `Function`.
    container.registerInstance('IGitHubIssueWriter', new GitHubIssueWriter());
    container.registerSingleton('IExternalIssueFetcher', GitHubIssueFetcher);
    container.register('IAllContributorsWriter', {
        useFactory: () => new AllContributorsWriter(workspaceRoot),
    });
    // DiscordOutreachPublisher's constructor takes two function-typed
    // parameters with sensible defaults. tsyringe cannot reflect on function
    // types (reflect-metadata emits `Function`), so `registerSingleton(...)`
    // crashes with "TypeInfo not known for Function". Register the concrete
    // instance directly so the default args are used.
    container.registerInstance('IOutreachPublisher', new DiscordOutreachPublisher());
    // Contributor action gate (NFR-5) — every contributor side-effect flows
    // through the supervisor before execution.
    container.registerSingleton('IContributorActionGate', SupervisorContributorActionGate);
    // Recap publishers are registered per-channel so the publish use case can
    // resolve the right adapter for a given target. Each publisher is ALSO
    // registered under the bare `IRecapPublisher` token so `@injectAll(...)`
    // in `PublishMonthlyRecapUseCase` picks up the full set.
    const registerRecapPublisher = (channel, factory) => {
        container.register(`IRecapPublisher:${channel}`, { useFactory: factory });
        container.register('IRecapPublisher', { useFactory: factory });
    };
    registerRecapPublisher(RecapChannel.File, () => new FileRecapPublisher(workspaceRoot));
    registerRecapPublisher(RecapChannel.Discord, (c) => new DiscordRecapPublisher(c.resolve('IOutreachPublisher')));
    // GithubDiscussionRecapPublisher also has a function-typed default-arg
    // constructor; instantiate it directly to bypass tsyringe reflection.
    registerRecapPublisher(RecapChannel.GithubDiscussion, () => new GithubDiscussionRecapPublisher());
    container.registerSingleton('IDiagnosticRunner', DiagnosticRunner);
    // ─── Doctor diagnostics (feature 097, phase 3) ──────────────────────
    // Each diagnostic is a strategy resolved by string token. The use case
    // injects the array via `resolveAll('IDiagnostic')`. Order is the
    // declaration order below — that's the order they are reported in.
    const registerDiagnostic = (factory) => container.register('IDiagnostic', { useFactory: factory });
    registerDiagnostic(() => new NodeVersionDiagnostic());
    registerDiagnostic(() => new PnpmInstalledDiagnostic());
    registerDiagnostic(() => new GitInstalledDiagnostic());
    registerDiagnostic((c) => c.resolve(GhCliAuthDiagnostic));
    registerDiagnostic((c) => c.resolve(AgentCliAvailabilityDiagnostic));
    registerDiagnostic((c) => new DotenvPresenceDiagnostic(c.resolve('IFileSystemService'), workspaceRoot));
    registerDiagnostic((c) => new WorkingTreeCleanDiagnostic(c.resolve('IGitPrService'), workspaceRoot));
    registerDiagnostic((c) => c.resolve(MigrationStatusDiagnostic));
    registerDiagnostic((c) => new TypespecFreshnessDiagnostic(c.resolve('IFileSystemService'), workspaceRoot));
    registerDiagnostic((c) => new DiGraphValidationDiagnostic((token) => c.isRegistered(token)));
}
