import express, { type Request, type Response } from 'express';
import type { Repository } from '../db/repository.js';
import type { BrowserSessionRepo } from '../db/browser-session-repo.js';
import { canEditInSpace } from './visibility.js';
import type { LocalTasksApiOptions } from './local-tasks-api.js';

/**
 * Shared dependencies threaded into each local-tasks route module.
 *
 * Built once in mountLocalTasksApi and passed to every registerLocalTask*Routes
 * call so the route groups stay decoupled while sharing the same repo/config
 * surface. `opts` carries the optional hooks (generateTitle / selectPiece /
 * pieceExists / evaluatePrompt / getMaxUploadMb …) used by individual routes.
 */
export interface LocalTasksDeps {
  repo: Repository;
  worktreeDir?: string;
  /** Resolved per-user folder root (opts.userFolderRoot ?? './data/users'). */
  userFolderRoot: string;
  sessRepo?: BrowserSessionRepo;
  /**
   * No-auth single-user mode owner ('local'), or undefined in auth mode so the
   * real `req.user.id` is used. See LocalTasksApiOptions.authActive.
   */
  noAuthOwner: string | undefined;
  opts: LocalTasksApiOptions;
}

/**
 * 3-valued write gate for shared-workspace tasks:
 *   'allow'      : owner/admin or editor+ space member → write permitted
 *   'view-only'  : can view but lacks write permission (space viewer role) → 403
 *   'deny'       : cannot even view (non-member / others' private) → 404
 */
export type WriteGate = 'allow' | 'view-only' | 'deny';

/**
 * Build the dynamic JSON body parser whose size limit is resolved per request
 * from the current config (so config changes take effect without a restart).
 * Clamped to [1, 1000] MB. Default 50.
 */
export function makeDynamicJson(getMaxUploadMb?: () => number) {
  const resolveUploadLimit = (): string => {
    const raw = getMaxUploadMb?.() ?? 50;
    const mb = Number.isFinite(raw) ? Math.max(1, Math.min(1000, Math.floor(raw))) : 50;
    return `${mb}mb`;
  };
  return (req: Request, res: Response, next: express.NextFunction) =>
    express.json({ limit: resolveUploadLimit() })(req, res, next);
}

/**
 * 共有ワークスペース対応の書き込みゲート。owner/admin に加え、タスクが属する
 * スペースの editor 以上のメンバーにも書き込みを許す。
 * owner-only のままにすべき破壊的操作（DELETE 等）はこのゲートを使わず
 * 従来通り checkTaskOwnership を使う。
 */
export async function resolveTaskWriteGate(
  repo: Repository,
  viewer: Express.User | undefined,
  task: { ownerId?: string | null; spaceId?: string | null } | null,
): Promise<WriteGate> {
  if (!task) return 'deny';
  // no-auth（viewer 不在）は従来通り素通り（synthetic local owner）。
  if (!viewer) return 'allow';
  if (viewer.role === 'admin') return 'allow';
  if (task.ownerId && task.ownerId === viewer.id) return 'allow';
  // スペースタスクならメンバーシップで判定。
  if (task.spaceId) {
    // getSpace は buildSpaceVisibilityWhere 経由。case スペースはメンバー
    // （editor/viewer どちらも）に可視なので、ここで space が取れる＝閲覧可。
    const space = await repo.getSpace(task.spaceId, { viewer });
    if (space) {
      const memberRole = repo.getSpaceMemberRole(task.spaceId, viewer.id);
      if (canEditInSpace(viewer, { ownerId: space.ownerId }, memberRole)) {
        return 'allow';
      }
      // 閲覧はできるが editor 未満（viewer ロール / 根オーナーでもない）→ 403。
      return 'view-only';
    }
  }
  return 'deny';
}
