#!/bin/bash

##===----------------------------------------------------------------------===##
##
## This source file is part of the OpenSwiftUI open source project
##
## Copyright (c) 2025 the OpenSwiftUI project authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
##
## SPDX-License-Identifier: Apache-2.0
##
##===----------------------------------------------------------------------===##

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
DOCS_DIR="$REPO_ROOT/.docs"
BUILD_DIR="$DOCS_DIR/build"
SYMBOL_GRAPH_DIR="$BUILD_DIR/symbol-graphs"
DOCC_OUTPUT_DIR="$BUILD_DIR/docc-output"

# Default configuration
PREVIEW_MODE=false
MINIMUM_ACCESS_LEVEL="public"
TARGET_NAME="OpenSwiftUI"
HOSTING_BASE_PATH=""
CLEAN_BUILD=false
PREVIEW_PORT=8000
SOURCE_SERVICE="github"
SOURCE_SERVICE_BASE_URL="https://github.com/OpenSwiftUIProject/OpenSwiftUI/blob/main"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

usage() {
    cat << EOF
Usage: $(basename "$0") [OPTIONS]

Build single-version Swift documentation using DocC with optional local preview.

For the canonical multi-version site, version selector, API Changes, and legacy
URL redirects, use Scripts/preview-documentation.sh instead.

OPTIONS:
    --preview                     Preview documentation locally with HTTP server
    --minimum-access-level LEVEL  Set minimum access level (public, package, internal, private, fileprivate)
                                  Default: public
    --target TARGET               Target to document (default: OpenSwiftUI)
    --hosting-base-path PATH      Base path for hosting (e.g., /OpenSwiftUI)
    --port PORT                   Port for preview server (default: 8000)
    --source-service SERVICE      Source service (github, gitlab, bitbucket)
    --source-service-base-url URL Base URL for source service
                                  (e.g., https://github.com/user/repo/blob/main)
    --clean                       Clean build artifacts and force rebuild
    -h, --help                    Show this help message

ENVIRONMENT:
    DOCC_HTML_DIR                 Use an existing swift-docc-render dist directory.
                                  When unset, DocC uses its bundled renderer.

EXAMPLES:
    # Build and preview the canonical multi-version site
    Scripts/preview-documentation.sh

    # Build and preview documentation (source links enabled by default)
    $(basename "$0") --preview

    # Preview with internal symbols on port 8080
    $(basename "$0") --preview --minimum-access-level internal --port 8080

    # Clean rebuild
    $(basename "$0") --preview --clean

    # Build for specific target
    $(basename "$0") --target OpenSwiftUICore --preview

    # Build with custom source service (e.g., for a fork)
    $(basename "$0") --preview \\
        --source-service github \\
        --source-service-base-url https://github.com/yourname/OpenSwiftUI/blob/custom-branch

EOF
    exit 0
}

log_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# Function to run docc command
rundocc() {
    if command -v xcrun >/dev/null 2>&1; then
        xcrun docc "$@"
    else
        docc "$@"
    fi
}

configure_docc_renderer() {
    if [[ -n "${DOCC_HTML_DIR:-}" ]]; then
        [[ -d "$DOCC_HTML_DIR" ]] || {
            log_error "DOCC_HTML_DIR does not exist: $DOCC_HTML_DIR"
            exit 1
        }
        export DOCC_HTML_DIR
        log_info "Using DocC renderer: $DOCC_HTML_DIR"
        return
    fi

    log_info "DocC Renderer: bundled default"
}

# Parse command line arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        --preview)
            PREVIEW_MODE=true
            shift
            ;;
        --minimum-access-level)
            MINIMUM_ACCESS_LEVEL="$2"
            shift 2
            ;;
        --target)
            TARGET_NAME="$2"
            shift 2
            ;;
        --hosting-base-path)
            HOSTING_BASE_PATH="$2"
            shift 2
            ;;
        --port)
            PREVIEW_PORT="$2"
            shift 2
            ;;
        --source-service)
            SOURCE_SERVICE="$2"
            shift 2
            ;;
        --source-service-base-url)
            SOURCE_SERVICE_BASE_URL="$2"
            shift 2
            ;;
        --clean)
            CLEAN_BUILD=true
            shift
            ;;
        -h|--help)
            usage
            ;;
        *)
            log_error "Unknown option: $1"
            usage
            ;;
    esac
done

# Validate minimum access level
case "$MINIMUM_ACCESS_LEVEL" in
    public|package|internal|private|fileprivate)
        ;;
    *)
        log_error "Invalid minimum access level: $MINIMUM_ACCESS_LEVEL"
        log_error "Valid values: public, package, internal, private, fileprivate"
        exit 1
        ;;
esac

log_info "Configuration:"
log_info "  Target: $TARGET_NAME"
log_info "  Minimum Access Level: $MINIMUM_ACCESS_LEVEL"
log_info "  Preview Mode: $PREVIEW_MODE"
if [[ "$PREVIEW_MODE" == true ]]; then
    log_info "  Preview Port: $PREVIEW_PORT"
fi
if [[ -n "$HOSTING_BASE_PATH" && "$PREVIEW_MODE" == true ]]; then
    log_warning "--hosting-base-path is ignored in preview mode; it is intended for static hosting."
elif [[ -n "$HOSTING_BASE_PATH" ]]; then
    log_info "  Hosting Base Path: $HOSTING_BASE_PATH"
fi
if [[ -n "$SOURCE_SERVICE" ]]; then
    log_info "  Source Service: $SOURCE_SERVICE"
    log_info "  Source Service Base URL: $SOURCE_SERVICE_BASE_URL"
fi
if [[ -n "${DOCC_HTML_DIR:-}" ]]; then
    log_info "  DocC Renderer: $DOCC_HTML_DIR"
else
    log_info "  DocC Renderer: bundled default"
fi

# Validate source service configuration
if [[ -n "$SOURCE_SERVICE" ]] && [[ -z "$SOURCE_SERVICE_BASE_URL" ]]; then
    log_error "--source-service requires --source-service-base-url"
    exit 1
fi

if [[ -z "$SOURCE_SERVICE" ]] && [[ -n "$SOURCE_SERVICE_BASE_URL" ]]; then
    log_error "--source-service-base-url requires --source-service"
    exit 1
fi

# Check for required tools
command -v swift >/dev/null 2>&1 || {
    log_error "swift is required but not installed. Aborting."
    exit 1
}

# Check for docc
if ! command -v docc >/dev/null 2>&1 && ! command -v xcrun >/dev/null 2>&1; then
    log_error "docc is required but not found. Please install Swift-DocC."
    exit 1
fi

# Clean build if requested
if [[ "$CLEAN_BUILD" == true ]]; then
    log_info "Cleaning build artifacts..."
    swift package clean
    rm -rf "$DOCS_DIR"
fi

# Create build directories
log_info "Preparing build directories..."
mkdir -p "$SYMBOL_GRAPH_DIR"
mkdir -p "$DOCC_OUTPUT_DIR"
configure_docc_renderer

# Step 1: Generate symbol graphs
cd "$REPO_ROOT"

# Use default .build directory for symbol graphs (reuses existing build)
SWIFT_BUILD_DIR=".build"
DEFAULT_SYMBOL_GRAPH_DIR="$SWIFT_BUILD_DIR/symbol-graphs"

# Check if symbol graphs already exist for the target
REBUILD_NEEDED=false
if [[ ! -f "$DEFAULT_SYMBOL_GRAPH_DIR/${TARGET_NAME}.symbols.json" ]]; then
    REBUILD_NEEDED=true
    log_info "No existing symbol graphs found for $TARGET_NAME, will perform clean build..."
else
    log_info "Found existing symbol graphs for $TARGET_NAME, reusing them (use --clean to rebuild)"
fi

if [[ "$REBUILD_NEEDED" == true ]] || [[ "$CLEAN_BUILD" == true ]]; then
    # Clean build to ensure symbol graphs are generated
    if [[ "$REBUILD_NEEDED" == true ]]; then
        log_info "Cleaning build to ensure symbol graph generation..."
        swift package clean
    fi

    log_info "Generating symbol graphs..."
    # Swift 6.2 crashes while indexing AppKit with the local private framework shims.
    swift build \
        --disable-index-store \
        --target "$TARGET_NAME" \
        -Xswiftc -emit-symbol-graph \
        -Xswiftc -emit-symbol-graph-dir \
        -Xswiftc "$DEFAULT_SYMBOL_GRAPH_DIR" \
        -Xswiftc -symbol-graph-minimum-access-level \
        -Xswiftc "$MINIMUM_ACCESS_LEVEL"

    if [[ ! -d "$DEFAULT_SYMBOL_GRAPH_DIR" ]] || [[ -z "$(ls -A "$DEFAULT_SYMBOL_GRAPH_DIR")" ]]; then
        log_error "Symbol graph generation failed or produced no output"
        exit 1
    fi
fi

# Filter symbol graphs for the target module
# Only include the target itself (which already includes re-exported OpenSwiftUICore symbols)
log_info "Filtering symbol graphs for $TARGET_NAME..."
if ls "$DEFAULT_SYMBOL_GRAPH_DIR/${TARGET_NAME}.symbols.json" >/dev/null 2>&1; then
    # Copy only the main target symbol graphs
    # OpenSwiftUI already includes OpenSwiftUICore symbols via @_exported import
    # Use explicit patterns to avoid matching OpenSwiftUICore*.symbols.json
    cp "$DEFAULT_SYMBOL_GRAPH_DIR/${TARGET_NAME}.symbols.json" "$SYMBOL_GRAPH_DIR/" 2>/dev/null || true
    cp "$DEFAULT_SYMBOL_GRAPH_DIR/${TARGET_NAME}@"*.symbols.json "$SYMBOL_GRAPH_DIR/" 2>/dev/null || true
    log_info "Symbol graphs for $TARGET_NAME copied successfully"

    # Filter out symbols from unwanted modules (CoreFoundation, CoreGraphics, etc.)
    log_info "Removing re-exported system framework symbols..."
    python3 << EOF
import json
import sys
import os

def filter_symbol_graph(file_path, allowed_modules):
    """Filter symbol graph to only include symbols from allowed modules."""
    try:
        with open(file_path, 'r') as f:
            data = json.load(f)

        if 'symbols' in data and isinstance(data['symbols'], list):
            original_count = len(data['symbols'])
            # Filter symbols by extracting module from precise identifier
            filtered_symbols = []
            for symbol in data['symbols']:
                precise = symbol.get('identifier', {}).get('precise', '')
                module = None

                # Extract module from mangled Swift names
                if precise.startswith('s:'):
                    rest = precise[2:]
                    if rest and rest[0].isdigit():
                        i = 0
                        while i < len(rest) and rest[i].isdigit():
                            i += 1
                        if i > 0:
                            mod_len = int(rest[:i])
                            module = rest[i:i+mod_len]

                # Keep if it's from an allowed module
                if module and module in allowed_modules:
                    filtered_symbols.append(symbol)

            data['symbols'] = filtered_symbols
            filtered_count = len(filtered_symbols)

            # Write back
            with open(file_path, 'w') as f:
                json.dump(data, f)

            print(f"  {os.path.basename(file_path)}: {original_count} -> {filtered_count} symbols", file=sys.stderr)
            return True
        return False
    except Exception as e:
        print(f"  Error filtering {file_path}: {e}", file=sys.stderr)
        return False

# List of allowed modules (OpenSwiftUI and OpenSwiftUICore only)
allowed_modules = {'OpenSwiftUI', 'OpenSwiftUICore', '$TARGET_NAME'}

# Filter the main OpenSwiftUI symbol graph
symbol_file = '$SYMBOL_GRAPH_DIR/OpenSwiftUI.symbols.json'
if os.path.exists(symbol_file):
    filter_symbol_graph(symbol_file, allowed_modules)
EOF
else
    log_error "No symbol graphs found for $TARGET_NAME"
    log_error "Available symbol graphs:"
    ls "$DEFAULT_SYMBOL_GRAPH_DIR"/*.symbols.json 2>/dev/null || echo "  (none)"
    exit 1
fi

# Step 2: Find or create documentation catalog
DOCC_CATALOG=""
if [[ -d "Sources/$TARGET_NAME/${TARGET_NAME}.docc" ]]; then
    DOCC_CATALOG="Sources/$TARGET_NAME/${TARGET_NAME}.docc"
    log_info "Using documentation catalog: $DOCC_CATALOG"
else
    log_warning "No .docc catalog found for $TARGET_NAME"
    log_info "DocC will generate documentation from symbol graphs only"
fi

# Step 3: Build documentation
log_info "Building documentation archive..."

if [[ -n "$DOCC_CATALOG" ]]; then
    DOCC_ARGS=(
        "$DOCC_CATALOG"
        --emit-digest
        --transform-for-static-hosting
        --output-path "$DOCC_OUTPUT_DIR"
    )

    if [[ "$PREVIEW_MODE" == true ]]; then
        DOCC_ARGS+=(--port "$PREVIEW_PORT")
    fi

    if [[ -n "$HOSTING_BASE_PATH" && "$PREVIEW_MODE" == false ]]; then
        DOCC_ARGS+=(--hosting-base-path "$HOSTING_BASE_PATH")
    fi

    if [[ -n "$SOURCE_SERVICE" ]]; then
        DOCC_ARGS+=(--source-service "$SOURCE_SERVICE")
        DOCC_ARGS+=(--source-service-base-url "$SOURCE_SERVICE_BASE_URL")
        DOCC_ARGS+=(--checkout-path "$REPO_ROOT")
    fi

    if [[ -d "$SYMBOL_GRAPH_DIR" ]] && [[ -n "$(ls -A "$SYMBOL_GRAPH_DIR")" ]]; then
        DOCC_ARGS+=(--additional-symbol-graph-dir "$SYMBOL_GRAPH_DIR")
    fi

    # Enabled experimental features:
    DOCC_ARGS+=(--enable-experimental-overloaded-symbol-presentation)

    if [[ "$PREVIEW_MODE" == true ]]; then
        # Check if port is already in use
        if lsof -Pi :"$PREVIEW_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then
            log_warning "Port $PREVIEW_PORT is already in use"

            # Find the process using the port
            PORT_PID=$(lsof -Pi :"$PREVIEW_PORT" -sTCP:LISTEN -t)
            PORT_PROCESS=$(ps -p "$PORT_PID" -o command= 2>/dev/null || echo "Unknown process")

            log_info "Process using port $PREVIEW_PORT (PID $PORT_PID): $PORT_PROCESS"

            # Ask user if they want to kill it
            read -p "Do you want to kill this process and start the preview server? (y/N): " -n 1 -r
            echo

            if [[ $REPLY =~ ^[Yy]$ ]]; then
                log_info "Killing process $PORT_PID..."
                kill "$PORT_PID"
                sleep 1

                # Verify it's killed
                if lsof -Pi :"$PREVIEW_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then
                    log_error "Failed to kill process on port $PREVIEW_PORT"
                    exit 1
                fi
                log_info "Process killed successfully"
            else
                log_error "Cannot start preview server. Please free port $PREVIEW_PORT or use --port option"
                exit 1
            fi
        fi

        log_info "Starting documentation preview server on port $PREVIEW_PORT..."
        log_info "Press Ctrl+C to stop the server"
        rundocc preview "${DOCC_ARGS[@]}"
    else
        rundocc convert "${DOCC_ARGS[@]}"
    fi
else
    TEMP_DOCC_CATALOG="$BUILD_DIR/${TARGET_NAME}.docc"
    mkdir -p "$TEMP_DOCC_CATALOG"

    if [[ -d "$SYMBOL_GRAPH_DIR" ]] && [[ -n "$(ls -A "$SYMBOL_GRAPH_DIR")" ]]; then
        cp "$SYMBOL_GRAPH_DIR"/*.symbols.json "$TEMP_DOCC_CATALOG/"
    fi

    DOCC_ARGS=(
        "$TEMP_DOCC_CATALOG"
        --emit-digest
        --transform-for-static-hosting
        --output-path "$DOCC_OUTPUT_DIR"
    )

    if [[ "$PREVIEW_MODE" == true ]]; then
        DOCC_ARGS+=(--port "$PREVIEW_PORT")
    fi

    if [[ -n "$HOSTING_BASE_PATH" && "$PREVIEW_MODE" == false ]]; then
        DOCC_ARGS+=(--hosting-base-path "$HOSTING_BASE_PATH")
    fi

    if [[ -n "$SOURCE_SERVICE" ]]; then
        DOCC_ARGS+=(--source-service "$SOURCE_SERVICE")
        DOCC_ARGS+=(--source-service-base-url "$SOURCE_SERVICE_BASE_URL")
        DOCC_ARGS+=(--checkout-path "$REPO_ROOT")
    fi

    if [[ "$PREVIEW_MODE" == true ]]; then
        # Check if port is already in use
        if lsof -Pi :"$PREVIEW_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then
            log_warning "Port $PREVIEW_PORT is already in use"

            # Find the process using the port
            PORT_PID=$(lsof -Pi :"$PREVIEW_PORT" -sTCP:LISTEN -t)
            PORT_PROCESS=$(ps -p "$PORT_PID" -o command= 2>/dev/null || echo "Unknown process")

            log_info "Process using port $PREVIEW_PORT (PID $PORT_PID): $PORT_PROCESS"

            # Ask user if they want to kill it
            read -p "Do you want to kill this process and start the preview server? (y/N): " -n 1 -r
            echo

            if [[ $REPLY =~ ^[Yy]$ ]]; then
                log_info "Killing process $PORT_PID..."
                kill "$PORT_PID"
                sleep 1

                # Verify it's killed
                if lsof -Pi :"$PREVIEW_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then
                    log_error "Failed to kill process on port $PREVIEW_PORT"
                    exit 1
                fi
                log_info "Process killed successfully"
            else
                log_error "Cannot start preview server. Please free port $PREVIEW_PORT or use --port option"
                exit 1
            fi
        fi

        log_info "Starting documentation preview server on port $PREVIEW_PORT..."
        log_info "Press Ctrl+C to stop the server"
        rundocc preview "${DOCC_ARGS[@]}"
    else
        rundocc convert "${DOCC_ARGS[@]}"
    fi
fi

if [[ "$PREVIEW_MODE" == false ]]; then
    log_info "Documentation built successfully"
    log_info "Documentation output: $DOCC_OUTPUT_DIR"
fi

log_info "Done!"
