# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

"""
Pydantic models for extension configuration and manifest validation.

Two distinct schemas:
- ExtensionConfig: Source configuration (extension.json) authored by developers
- Manifest: Built output (manifest.json) generated by the build tool
"""

from __future__ import annotations

from typing import Any

from pydantic import BaseModel, Field  # noqa: I001

# =============================================================================
# Shared components
# =============================================================================


class ModuleFederationConfig(BaseModel):
    """Configuration for Webpack Module Federation."""

    exposes: list[str] = Field(
        default_factory=list,
        description="Modules exposed by this extension",
    )
    filename: str = Field(
        default="remoteEntry.js",
        description="Remote entry filename",
    )
    shared: dict[str, Any] = Field(
        default_factory=dict,
        description="Shared dependencies configuration",
    )
    remotes: dict[str, str] = Field(
        default_factory=dict,
        description="Remote module references",
    )


class ContributionConfig(BaseModel):
    """Configuration for frontend UI contributions.

    Views and menus use a nested structure: type -> scope -> location -> contributions.

    Example:
        {
            "views": {
                "sqllab": {
                    "panels": [{"id": "my-ext.panel", "name": "My Panel"}],
                    "leftSidebar": [{"id": "my-ext.sidebar", "name": "Sidebar"}]
                }
            },
            "menus": {
                "sqllab": {
                    "editor": {"primary": [...], "secondary": [...]}
                }
            }
        }
    """

    commands: list[dict[str, Any]] = Field(
        default_factory=list,
        description="Command contributions",
    )
    views: dict[str, dict[str, list[dict[str, Any]]]] = Field(
        default_factory=dict,
        description="View contributions by scope and location",
    )
    menus: dict[str, dict[str, Any]] = Field(
        default_factory=dict,
        description="Menu contributions by scope and location",
    )


class BaseExtension(BaseModel):
    """Base fields shared by ExtensionConfig and Manifest."""

    id: str = Field(
        ...,
        description="Unique extension identifier",
        min_length=1,
    )
    name: str = Field(
        ...,
        description="Human-readable extension name",
        min_length=1,
    )
    version: str = Field(
        default="0.0.0",
        description="Semantic version string",
        pattern=r"^\d+\.\d+\.\d+$",
    )
    license: str | None = Field(
        default=None,
        description="License identifier (e.g., 'Apache-2.0')",
    )
    description: str | None = Field(
        default=None,
        description="Extension description",
    )
    dependencies: list[str] = Field(
        default_factory=list,
        description="List of extension IDs this extension depends on",
    )
    permissions: list[str] = Field(
        default_factory=list,
        description="Permissions required by this extension",
    )


# =============================================================================
# ExtensionConfig (extension.json)
# =============================================================================


class ExtensionConfigFrontend(BaseModel):
    """Frontend section in extension.json."""

    contributions: ContributionConfig = Field(
        default_factory=ContributionConfig,
        description="UI contribution points",
    )
    moduleFederation: ModuleFederationConfig = Field(  # noqa: N815
        default_factory=ModuleFederationConfig,
        description="Module Federation configuration",
    )


class ExtensionConfigBackend(BaseModel):
    """Backend section in extension.json."""

    entryPoints: list[str] = Field(  # noqa: N815
        default_factory=list,
        description="Python module entry points to load",
    )
    files: list[str] = Field(
        default_factory=list,
        description="Glob patterns for backend Python files",
    )


class ExtensionConfig(BaseExtension):
    """
    Schema for extension.json (source configuration).

    This file is authored by developers to define extension metadata.
    """

    frontend: ExtensionConfigFrontend | None = Field(
        default=None,
        description="Frontend configuration",
    )
    backend: ExtensionConfigBackend | None = Field(
        default=None,
        description="Backend configuration",
    )


# =============================================================================
# Manifest (manifest.json)
# =============================================================================


class ManifestFrontend(BaseModel):
    """Frontend section in manifest.json."""

    contributions: ContributionConfig = Field(
        default_factory=ContributionConfig,
        description="UI contribution points",
    )
    moduleFederation: ModuleFederationConfig = Field(  # noqa: N815
        default_factory=ModuleFederationConfig,
        description="Module Federation configuration",
    )
    remoteEntry: str = Field(  # noqa: N815
        ...,
        description="Path to the built remote entry file",
    )


class ManifestBackend(BaseModel):
    """Backend section in manifest.json."""

    entryPoints: list[str] = Field(  # noqa: N815
        default_factory=list,
        description="Python module entry points to load",
    )


class Manifest(BaseExtension):
    """
    Schema for manifest.json (built output).

    This file is generated by the build tool from extension.json.
    """

    frontend: ManifestFrontend | None = Field(
        default=None,
        description="Frontend manifest",
    )
    backend: ManifestBackend | None = Field(
        default=None,
        description="Backend manifest",
    )
