// Protocol Buffers - Google's data interchange format
// Copyright 2026 Google LLC.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

use super::*;
use crate::__internal::entity_tag::*;
use crate::__internal::{EntityType, Enum, Private, Singular};
use crate::extension::{ExtAccess, ExtClear, ExtGetMut, ExtHas};
use crate::{ExtensionId, IntoMut, IntoView, MessageViewInterop, Proxied};
use std::sync::LazyLock;

pub struct InnerExtensionId {
    mini_table: &'static LazyLock<MiniTableExtensionInitPtr>,
}

impl InnerExtensionId {
    pub const fn new(mini_table: &'static LazyLock<MiniTableExtensionInitPtr>) -> Self {
        Self { mini_table }
    }

    pub fn mini_table(&self) -> RawMiniTableExtension {
        self.mini_table.0
    }
}

#[linkme::distributed_slice]
pub static EXTENSIONS: [LazyLock<MiniTableExtensionInitPtr>];

#[cfg(all(target_family = "unix", not(target_os = "macos")))]
core::arch::global_asm!(
    r#"
    .section linkme_EXTENSIONS,"awR",@progbits
    .globl LINKME_SENTINEL_EXTENSIONS
    LINKME_SENTINEL_EXTENSIONS:
    .size   LINKME_SENTINEL_EXTENSIONS, 0

    .section linkm2_EXTENSIONS,"awR",@progbits
    .globl LINKM2_SENTINEL_EXTENSIONS
    LINKM2_SENTINEL_EXTENSIONS:
    .size   LINKM2_SENTINEL_EXTENSIONS, 0
    "#
);

pub fn generated_extension_registry() -> RawExtensionRegistry {
    static EXTENSIONS_REGISTRY: LazyLock<ExtensionRegistryInitPtr> = LazyLock::new(|| unsafe {
        let registry = upb_ExtensionRegistry_New(THREAD_LOCAL_ARENA.with(|a| a.raw()));
        for extension in EXTENSIONS {
            upb_ExtensionRegistry_Add(registry, extension.0);
        }
        ExtensionRegistryInitPtr(registry)
    });
    EXTENSIONS_REGISTRY.0
}

impl<Msg: Message, T: Singular> ExtHas<Msg> for ExtensionId<Msg, T> {
    fn has(&self, _private: Private, msg: impl AsView<Proxied = Msg>) -> bool {
        // SAFETY:
        // - `msg` is a valid message.
        // - `mini_table` is the one associated with `self`.
        unsafe {
            upb_Message_HasExtension(
                msg.as_view().get_ptr(Private).raw(),
                self.inner.mini_table().as_ptr(),
            )
        }
    }
}

impl<Extendee: Message, V: Message> ExtAccess<Extendee, V, MessageTag>
    for ExtensionId<Extendee, V>
{
    fn get<'msg>(
        &self,
        _private: Private,
        msg: impl IntoView<'msg, Proxied = Extendee>,
    ) -> View<'msg, V> {
        let msg = msg.into_view();
        let default_instance = <V as Proxied>::View::default();
        let raw_msg = unsafe {
            upb_Message_GetExtensionMessage(
                msg.get_ptr(Private).raw(),
                self.inner.mini_table().as_ptr(),
                default_instance.get_ptr(Private).raw(),
            )
        };
        unsafe {
            <View<'msg, V>>::__unstable_wrap_raw_message_unchecked_lifetime(
                raw_msg.as_ptr() as *const std::ffi::c_void
            )
        }
    }

    fn set(
        &self,
        _private: Private,
        mut msg: impl AsMut<MutProxied = Extendee>,
        value: impl IntoProxied<V>,
    ) {
        let mut msg_mut = msg.as_mut();

        let mut child = value.into_proxied(Private);
        let child_ptr = child.get_ptr_mut(Private);

        // Ensure child arena is fused into parent message
        msg_mut.get_arena(Private).fuse(child.get_arena(Private));

        unsafe {
            assert!(upb_Message_SetExtensionMessage(
                msg_mut.get_ptr_mut(Private).raw(),
                self.inner.mini_table().as_ptr(),
                child_ptr.raw(),
                msg_mut.get_arena(Private).raw(),
            ));
        }
    }
}

impl<Extendee: Message, V: Message> ExtGetMut<Extendee, V, MessageTag>
    for ExtensionId<Extendee, V>
{
    fn get_mut<'msg>(
        &self,
        _private: Private,
        msg: impl IntoMut<'msg, MutProxied = Extendee>,
    ) -> Mut<'msg, V> {
        let mut msg = msg.into_mut();
        unsafe {
            // SAFETY: The arena associated with a `Mut<'msg, _>` proxy lives for at least `'msg`.
            // `UpbGetArena::get_arena` returns an `&Arena` tied to the local borrow of `msg`,
            // but we can safely extend it back to `'msg` here because `msg` holds an `&'msg Arena`.
            let arena_ref: &'msg Arena = &*(msg.get_arena(Private) as *const Arena);

            // TODO: upb should have a GetOrCreateExtension operation instead of this dance.
            let raw_msg = match upb_Message_HasExtension(
                msg.get_ptr_mut(Private).raw(),
                self.inner.mini_table().as_ptr(),
            ) {
                true => {
                    upb_Message_GetExtensionMessage(
                        msg.get_ptr_mut(Private).raw(),
                        self.inner.mini_table().as_ptr(),
                        NonNull::dangling(), // Not used.
                    )
                }
                false => {
                    let raw_msg =
                        MessagePtr::<V>::new(arena_ref).expect("alloc should never fail").raw();
                    upb_Message_SetExtensionMessage(
                        msg.get_ptr_mut(Private).raw(),
                        self.inner.mini_table().as_ptr(),
                        raw_msg,
                        arena_ref.raw(),
                    );
                    raw_msg
                }
            };
            V::from_message_mut(raw_msg, arena_ref)
        }
    }
}

impl<Extendee: Message, V: Singular> ExtAccess<Extendee, Repeated<V>, RepeatedTag>
    for ExtensionId<Extendee, Repeated<V>>
where
    V: EntityType + UpbTypeConversions<V::Tag>,
{
    fn get<'msg>(
        &self,
        _private: Private,
        msg: impl IntoView<'msg, Proxied = Extendee>,
    ) -> View<'msg, Repeated<V>> {
        let msg = msg.into_view();
        let raw_array = unsafe {
            upb_Message_GetExtensionArray(
                msg.get_ptr(Private).raw(),
                self.inner.mini_table().as_ptr(),
            )
        };
        unsafe {
            IntoView::into_view(
                raw_array.map_or_else(empty_array::<V>, |raw| RepeatedView::from_raw(Private, raw)),
            )
        }
    }

    fn set(
        &self,
        _private: Private,
        mut msg: impl AsMut<MutProxied = Extendee>,
        value: impl IntoProxied<Repeated<V>>,
    ) {
        let value = value.into_proxied(Private);
        let mut ext_mut = self.get_mut(msg.as_mut());
        ext_mut.clear();
        ext_mut.copy_from(unsafe { RepeatedView::from_raw(Private, value.inner(Private).raw()) });
    }
}

impl<Extendee: Message, V> ExtGetMut<Extendee, Repeated<V>, RepeatedTag>
    for ExtensionId<Extendee, Repeated<V>>
where
    V: Singular + EntityType + UpbTypeConversions<V::Tag>,
{
    fn get_mut<'msg>(
        &self,
        _private: Private,
        msg: impl IntoMut<'msg, MutProxied = Extendee>,
    ) -> Mut<'msg, Repeated<V>> {
        let mut msg = msg.into_mut();

        // SAFETY: The arena associated with a `Mut<'msg, _>` proxy lives for at least `'msg`.
        // `UpbGetArena::get_arena` returns an `&Arena` tied to the local borrow of `msg`,
        // but we can safely extend it back to `'msg` here because `msg` holds an `&'msg Arena`.
        let arena_ref: &'msg Arena = unsafe { &*(msg.get_arena(Private) as *const Arena) };
        let raw_array = unsafe {
            upb_Message_GetExtensionMutableArray(
                msg.get_ptr_mut(Private).raw(),
                self.inner.mini_table().as_ptr(),
            )
        };
        let raw_array = raw_array.unwrap_or_else(|| unsafe {
            let new_arr = upb_Array_New(arena_ref.raw(), V::upb_type());
            let mut ptr = new_arr.as_ptr();
            upb_Message_SetExtension(
                msg.get_ptr_mut(Private).raw(),
                self.inner.mini_table().as_ptr(),
                &mut ptr as *mut _ as *const core::ffi::c_void,
                arena_ref.raw(),
            );
            new_arr
        });
        unsafe {
            RepeatedMut::from_inner(Private, InnerRepeatedMut::new(raw_array, arena_ref)).into_mut()
        }
    }
}

macro_rules! impl_upb_scalar_extension {
    ($($t:ty => [$get:ident, $set:ident]),* $(,)?) => {
        $(
            impl<Extendee: Message> ExtAccess<Extendee, $t, PrimitiveTag> for ExtensionId<Extendee, $t> {
                fn get<'msg>(&self, _private: Private, msg: impl IntoView<'msg, Proxied = Extendee>) -> View<'msg, $t> {
                    let msg = msg.into_view();
                    unsafe {
                        $get(
                            msg.get_ptr(Private).raw(),
                            self.inner.mini_table().as_ptr(),
                            self.default.expect("scalar extensions must have a default value"),
                        )
                    }
                }

                fn set(&self, _private: Private, mut msg: impl AsMut<MutProxied = Extendee>, value: impl IntoProxied<$t>) {
                    let mut msg_mut = msg.as_mut();
                    unsafe {
                        assert!($set(
                            msg_mut.get_ptr_mut(Private).raw(),
                            self.inner.mini_table().as_ptr(),
                            value.into_proxied(Private),
                            msg_mut.get_arena(Private).raw(),
                        ));
                    }
                }
            }
        )*
    };
}

impl_upb_scalar_extension!(
    bool => [upb_Message_GetExtensionBool, upb_Message_SetExtensionBool],
    f32 => [upb_Message_GetExtensionFloat, upb_Message_SetExtensionFloat],
    f64 => [upb_Message_GetExtensionDouble, upb_Message_SetExtensionDouble],
    i32 => [upb_Message_GetExtensionInt32, upb_Message_SetExtensionInt32],
    i64 => [upb_Message_GetExtensionInt64, upb_Message_SetExtensionInt64],
    u32 => [upb_Message_GetExtensionUInt32, upb_Message_SetExtensionUInt32],
    u64 => [upb_Message_GetExtensionUInt64, upb_Message_SetExtensionUInt64],
);

impl<Extendee: Message> ExtAccess<Extendee, ProtoString, PrimitiveTag>
    for ExtensionId<Extendee, ProtoString>
{
    fn get<'msg>(
        &self,
        _private: Private,
        msg: impl IntoView<'msg, Proxied = Extendee>,
    ) -> View<'msg, ProtoString> {
        let msg = msg.into_view();
        let upb_str_view = unsafe {
            upb_Message_GetExtensionString(
                msg.get_ptr(Private).raw(),
                self.inner.mini_table().as_ptr(),
                self.default.expect("string extensions must have a default value").into(),
            )
        };
        unsafe { ProtoStr::from_utf8_unchecked(upb_str_view.as_ref()) }
    }

    fn set(
        &self,
        _private: Private,
        mut msg: impl AsMut<MutProxied = Extendee>,
        value: impl IntoProxied<ProtoString>,
    ) {
        let s = value.into_proxied(Private);
        let (view, arena) = s.into_inner(Private).into_raw_parts();
        let mut msg_mut = msg.as_mut();
        msg_mut.get_arena(Private).fuse(&arena);
        unsafe {
            assert!(upb_Message_SetExtensionString(
                msg_mut.get_ptr_mut(Private).raw(),
                self.inner.mini_table().as_ptr(),
                view,
                msg_mut.get_arena(Private).raw(),
            ));
        }
    }
}

impl<Extendee: Message> ExtAccess<Extendee, ProtoBytes, PrimitiveTag>
    for ExtensionId<Extendee, ProtoBytes>
{
    fn get<'msg>(
        &self,
        _private: Private,
        msg: impl IntoView<'msg, Proxied = Extendee>,
    ) -> View<'msg, ProtoBytes> {
        let msg = msg.into_view();
        let upb_str_view = unsafe {
            upb_Message_GetExtensionString(
                msg.get_ptr(Private).raw(),
                self.inner.mini_table().as_ptr(),
                self.default.expect("bytes extensions must have a default value").into(),
            )
        };
        unsafe { upb_str_view.as_ref() }
    }

    fn set(
        &self,
        _private: Private,
        mut msg: impl AsMut<MutProxied = Extendee>,
        value: impl IntoProxied<ProtoBytes>,
    ) {
        let s = value.into_proxied(Private);
        let (view, arena) = s.into_inner(Private).into_raw_parts();
        let mut msg_mut = msg.as_mut();
        msg_mut.get_arena(Private).fuse(&arena);
        unsafe {
            assert!(upb_Message_SetExtensionString(
                msg_mut.get_ptr_mut(Private).raw(),
                self.inner.mini_table().as_ptr(),
                view,
                msg_mut.get_arena(Private).raw(),
            ));
        }
    }
}

impl<Msg: Message, T: Proxied> ExtClear<Msg> for ExtensionId<Msg, T> {
    fn clear(&self, _private: Private, mut msg: impl AsMut<MutProxied = Msg>) {
        // SAFETY:
        // - `msg` is a valid message.
        // - `mini_table` is the one associated with `self`.
        unsafe {
            upb_Message_ClearExtension(
                msg.as_mut().get_ptr_mut(Private).raw(),
                self.inner.mini_table().as_ptr(),
            )
        }
    }
}

impl<Extendee: Message, E> ExtAccess<Extendee, E, EnumTag> for ExtensionId<Extendee, E>
where
    E: Enum + Proxied + EntityType<Tag = EnumTag> + Copy,
    for<'a> E: Proxied<View<'a> = E>,
    for<'a> Extendee::MessageView<'a>: UpbGetMessagePtr,
    i32: From<E>,
{
    fn get<'msg>(
        &self,
        _private: Private,
        msg: impl IntoView<'msg, Proxied = Extendee>,
    ) -> View<'msg, E> {
        let msg = msg.into_view();
        let default = self.default.expect("enum extensions must have a default value");
        let val = unsafe {
            upb_Message_GetExtensionInt32(
                msg.get_ptr(Private).raw(),
                self.inner.mini_table().as_ptr(),
                i32::from(default),
            )
        };
        E::try_from(val).unwrap_or(default)
    }

    fn set(
        &self,
        _private: Private,
        mut msg: impl AsMut<MutProxied = Extendee>,
        value: impl IntoProxied<E>,
    ) {
        let mut msg_mut = msg.as_mut();
        unsafe {
            assert!(upb_Message_SetExtensionInt32(
                msg_mut.get_ptr_mut(Private).raw(),
                self.inner.mini_table().as_ptr(),
                value.into_proxied(Private).into(),
                msg_mut.get_arena(Private).raw(),
            ));
        }
    }
}
