/// Abstract interface for reading/writing vault files on a git host.
///
/// Both Gitea and GitHub expose a "repo contents" REST API that lets us
/// read, write, and delete individual files without cloning the repo.
/// This interface captures just the operations the vault needs.

export interface GitHost {
  /// Read a single file from the repo, returning its raw bytes.
  readFile(path: string): Promise<Uint8Array>;

  /// Create or update a file in the repo with a commit message.
  writeFile(path: string, content: Uint8Array, message: string): Promise<void>;

  /// Like writeFile, but throws if the file already exists. Used by setup
  /// wizard to refuse to clobber existing vault state. Implementation must
  /// pre-check existence and only POST/PUT-create — never include a sha.
  writeFileCreateOnly(path: string, content: Uint8Array, message: string): Promise<void>;

  /// Delete a file from the repo with a commit message.
  deleteFile(path: string, message: string): Promise<void>;

  /// List file names in a directory (non-recursive).
  listDir(path: string): Promise<string[]>;

  /// Best-effort: returns metadata for the most recent commit touching `path`.
  /// Returns null if the path has no commits, the API fails, or the host
  /// doesn't support the lookup. Callers must tolerate null.
  lastCommit(path: string): Promise<{ sha: string; author: string; date: string } | null>;

  /// Write an opaque binary blob to the repo. Optimized for large
  /// attachments — implementations switch from Contents API to Git
  /// Data API when content exceeds BLOB_THRESHOLD_BYTES.
  /// Returns the path that was written (same as input, for chaining).
  putBlob(path: string, content: Uint8Array, message: string): Promise<string>;

  /// Read an opaque binary blob from the repo. Same semantics as
  /// readFile for current sizes. Distinct method so we can later add
  /// streaming/chunked reads for very large blobs.
  getBlob(path: string): Promise<Uint8Array>;

  /// Delete a blob from the repo. Currently identical to deleteFile;
  /// kept distinct for symmetry with putBlob.
  deleteBlob(path: string, message: string): Promise<void>;

  /// Cached sync metadata, populated by the `sync` handler — get_vault_status
  /// reads these without any network call. lastSyncAt is unix SECONDS (or null
  /// until the first sync). ahead/behind exist for parity with `relicario
  /// status`; the extension writes straight to the host (no local commit
  /// graph), so in practice they stay 0.
  lastSyncAt: number | null;
  ahead: number;
  behind: number;
}

/// Pre-base64 byte size at which putBlob switches from Contents API to
/// Git Data API. 900 KB leaves headroom for base64 inflation (1.33×) and
/// JSON wrapping under both GitHub's and Gitea's Contents API soft-limits.
export const BLOB_THRESHOLD_BYTES = 900 * 1024;

/// Convert a Uint8Array to a base64 string (works in service worker context).
export function uint8ArrayToBase64(bytes: Uint8Array): string {
  let binary = '';
  for (let i = 0; i < bytes.length; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

/// Convert a base64 string to a Uint8Array.
export function base64ToUint8Array(base64: string): Uint8Array {
  const binary = atob(base64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes;
}

/// Factory function that returns the appropriate GitHost implementation.
import { GiteaHost } from './gitea';
import { GitHubHost } from './github';

export function createGitHost(
  hostType: 'gitea' | 'github',
  hostUrl: string,
  repoPath: string,
  apiToken: string,
): GitHost {
  if (hostType === 'gitea') {
    return new GiteaHost(hostUrl, repoPath, apiToken);
  }
  return new GitHubHost(repoPath, apiToken);
}
