import { TabViewItem as TabViewItemDefinition } from '.';
import { Font } from '../styling/font';

import { IOSHelper, View } from '../core/view';
import { ViewBase } from '../core/view-base';
import { TabViewBase, TabViewItemBase, itemsProperty, selectedIndexProperty, tabTextColorProperty, tabTextFontSizeProperty, tabBackgroundColorProperty, selectedTabTextColorProperty, iosIconRenderingModeProperty, traceMissingIcon, iosBottomAccessoryProperty, iosTabBarMinimizeBehaviorProperty } from './tab-view-common';
import { Color } from '../../color';
import { Trace } from '../../trace';
import { fontInternalProperty } from '../styling/style-properties';
import { textTransformProperty, getTransformedText } from '../text-base';
import { CoreTypes } from '../../core-types';
import { ImageSource } from '../../image-source';
import { profile } from '../../profiling';
import { Frame } from '../frame';
import { layout } from '../../utils/layout-helper';
import { FONT_PREFIX, isFontIconURI, isSystemURI, SYSTEM_PREFIX } from '../../utils/common';
import { SDK_VERSION } from '../../utils/constants';
import { Device, Screen } from '../../platform';
export * from './tab-view-common';

@NativeClass
class UITabBarControllerImpl extends UITabBarController {
	private _owner: WeakRef<TabView>;

	public static initWithOwner(owner: WeakRef<TabView>): UITabBarControllerImpl {
		const handler = <UITabBarControllerImpl>UITabBarControllerImpl.new();
		handler._owner = owner;

		return handler;
	}

	public viewDidLoad(): void {
		super.viewDidLoad();

		// Unify translucent and opaque bars layout
		// this.edgesForExtendedLayout = UIRectEdgeBottom;
		this.extendedLayoutIncludesOpaqueBars = true;
	}

	@profile
	public viewWillAppear(animated: boolean): void {
		super.viewWillAppear(animated);
		const owner = this._owner?.deref();
		if (!owner) {
			return;
		}

		IOSHelper.updateAutoAdjustScrollInsets(this, owner);

		if (!owner.parent) {
			owner.callLoaded();
		}
	}

	@profile
	public viewDidDisappear(animated: boolean): void {
		super.viewDidDisappear(animated);
		const owner = this._owner?.deref();
		if (owner && !owner.parent && owner.isLoaded && !this.presentedViewController) {
			owner.callUnloaded();
		}
	}

	public viewWillTransitionToSizeWithTransitionCoordinator(size: CGSize, coordinator: UIViewControllerTransitionCoordinator): void {
		super.viewWillTransitionToSizeWithTransitionCoordinator(size, coordinator);
		coordinator.animateAlongsideTransitionCompletion(null, () => {
			const owner = this._owner?.deref();
			if (owner && owner.items) {
				owner.items.forEach((tabItem) => tabItem._updateTitleAndIconPositions());
			}
		});
	}

	// Mind implementation for other controllers
	public traitCollectionDidChange(previousTraitCollection: UITraitCollection): void {
		super.traitCollectionDidChange(previousTraitCollection);

		const owner = this._owner?.deref();
		if (owner) {
			if (SDK_VERSION >= 13) {
				if (this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection && this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection(previousTraitCollection)) {
					owner.notify({
						eventName: IOSHelper.traitCollectionColorAppearanceChangedEvent,
						object: owner,
					});
				}
			}

			if (this.traitCollection.layoutDirection !== previousTraitCollection.layoutDirection) {
				owner.notify({
					eventName: IOSHelper.traitCollectionLayoutDirectionChangedEvent,
					object: owner,
				});
			}
		}
	}
}

@NativeClass
class UITabBarControllerDelegateImpl extends NSObject implements UITabBarControllerDelegate {
	public static ObjCProtocols = [UITabBarControllerDelegate];

	private _owner: WeakRef<TabView>;

	public static initWithOwner(owner: WeakRef<TabView>): UITabBarControllerDelegateImpl {
		const delegate = <UITabBarControllerDelegateImpl>UITabBarControllerDelegateImpl.new();
		delegate._owner = owner;

		return delegate;
	}

	public tabBarControllerShouldSelectViewController(tabBarController: UITabBarController, viewController: UIViewController): boolean {
		if (Trace.isEnabled()) {
			Trace.write('TabView.delegate.SHOULD_select(' + tabBarController + ', ' + viewController + ');', Trace.categories.Debug);
		}

		const owner = this._owner?.deref();
		if (owner) {
			// "< More" cannot be visible after clicking on the main tab bar buttons.
			owner._handleTwoNavigationBars(false);
		}

		if (tabBarController.selectedViewController === viewController) {
			return false;
		}

		return true;
	}

	public tabBarControllerDidSelectViewController(tabBarController: UITabBarController, viewController: UIViewController): void {
		if (Trace.isEnabled()) {
			Trace.write('TabView.delegate.DID_select(' + tabBarController + ', ' + viewController + ');', Trace.categories.Debug);
		}

		const owner = this._owner?.deref();
		if (owner) {
			owner._onViewControllerShown(tabBarController, viewController);
		}
	}
}

@NativeClass
class UINavigationControllerDelegateImpl extends NSObject implements UINavigationControllerDelegate {
	public static ObjCProtocols = [UINavigationControllerDelegate];

	private _owner: WeakRef<TabView>;

	public static initWithOwner(owner: WeakRef<TabView>): UINavigationControllerDelegateImpl {
		const delegate = <UINavigationControllerDelegateImpl>UINavigationControllerDelegateImpl.new();
		delegate._owner = owner;

		return delegate;
	}

	navigationControllerWillShowViewControllerAnimated(navigationController: UINavigationController, viewController: UIViewController, animated: boolean): void {
		if (Trace.isEnabled()) {
			Trace.write('TabView.moreNavigationController.WILL_show(' + navigationController + ', ' + viewController + ', ' + animated + ');', Trace.categories.Debug);
		}

		const owner = this._owner?.deref();
		if (owner) {
			// If viewController is one of our tab item controllers, then "< More" will be visible shortly.
			// Otherwise viewController is the UIMoreListController which shows the list of all tabs beyond the 4th tab.
			const backToMoreWillBeVisible = navigationController.tabBarController?.viewControllers?.containsObject(viewController);
			owner._handleTwoNavigationBars(backToMoreWillBeVisible);
		}
	}

	navigationControllerDidShowViewControllerAnimated(navigationController: UINavigationController, viewController: UIViewController, animated: boolean): void {
		if (Trace.isEnabled()) {
			Trace.write('TabView.moreNavigationController.DID_show(' + navigationController + ', ' + viewController + ', ' + animated + ');', Trace.categories.Debug);
		}
		// We don't need Edit button in More screen.
		navigationController.navigationBar.topItem.rightBarButtonItem = null;
		const owner = this._owner?.deref();
		if (owner) {
			owner._onViewControllerShown(navigationController.tabBarController, viewController);
		}
	}
}

function updateTitleAndIconPositions(tabItem: TabViewItem, tabBarItem: UITabBarItem, controller: UIViewController) {
	if (!tabItem || !tabBarItem) {
		return;
	}

	// For iOS <11 icon is *always* above the text.
	// For iOS 11 icon is above the text *only* on phones in portrait mode.
	const orientation = controller.interfaceOrientation;
	const isPortrait = orientation !== UIInterfaceOrientation.LandscapeLeft && orientation !== UIInterfaceOrientation.LandscapeRight;
	const isIconAboveTitle = (!__VISIONOS__ && SDK_VERSION < 11) || (Device.deviceType === 'Phone' && isPortrait);

	if (!tabItem.iconSource) {
		if (isIconAboveTitle) {
			tabBarItem.titlePositionAdjustment = {
				horizontal: 0,
				vertical: -20,
			};
		} else {
			tabBarItem.titlePositionAdjustment = { horizontal: 0, vertical: 0 };
		}
	}

	if (!tabItem.title) {
		if (isIconAboveTitle) {
			tabBarItem.imageInsets = new UIEdgeInsets({
				top: 6,
				left: 0,
				bottom: -6,
				right: 0,
			});
		} else {
			tabBarItem.imageInsets = new UIEdgeInsets({
				top: 0,
				left: 0,
				bottom: 0,
				right: 0,
			});
		}
	}
}

export class TabViewItem extends TabViewItemBase {
	private __controller: UIViewController;

	public setViewController(controller: UIViewController, nativeView: UIView) {
		this.__controller = controller;
		this.setNativeView(nativeView);
	}

	public disposeNativeView() {
		this.__controller = undefined;
		this.setNativeView(undefined);
	}

	public loadView(view: ViewBase): void {
		const tabView = this.parent as TabViewBase;
		if (tabView && tabView.items) {
			const index = tabView.items.indexOf(this);

			if (index === tabView.selectedIndex) {
				super.loadView(view);
			}
		}
	}

	public _update() {
		const parent = <TabView>this.parent;
		const controller = this.__controller;
		if (parent && controller) {
			const icon = parent._getIcon(this);
			const index = parent.items.indexOf(this);
			const title = getTransformedText(this.title, this.style.textTransform);

			if (SDK_VERSION >= 18) {
				// iOS 18+: use UITab instead of UITabBarItem.
				// The UITab instances are created and managed at the TabView level,
				// so here we just update the corresponding tab for this controller.
				const identifier = `${index}`;
				const tabController = parent.viewController as UITabBarController;
				try {
					const tab = tabController.tabForIdentifier(identifier);
					if (tab) {
						tab.title = title;
						tab.image = icon;
					}
				} catch (e) {
					// Fallback: if tabForIdentifier is not available for some reason,
					// do not crash – rely on existing tab configuration.
				}
			} else {
				// iOS < 18: keep using UITabBarItem-based configuration.
				const tabBarItem = UITabBarItem.alloc().initWithTitleImageTag(title, icon, index);
				updateTitleAndIconPositions(this, tabBarItem, controller);

				// There is no need to request title styles update here in newer versions as styling is handled by bar appearance instance
				if (!__VISIONOS__ && SDK_VERSION < 15) {
					// TODO: Repeating code. Make TabViewItemBase - ViewBase and move the colorProperty on tabViewItem.
					// Delete the repeating code.
					const states = getTitleAttributesForStates(parent);
					applyStatesToItem(tabBarItem, states);
				}
				controller.tabBarItem = tabBarItem;
			}
		}
	}

	public _updateTitleAndIconPositions() {
		// UITab-based configuration (iOS 18+) does not expose the same per-item
		// title/icon positioning APIs as UITabBarItem, so we only adjust
		// positions when using the legacy UITabBarItem setup.
		if (SDK_VERSION >= 18 || !this.__controller || !this.__controller.tabBarItem) {
			return;
		}
		updateTitleAndIconPositions(this, this.__controller.tabBarItem, this.__controller);
	}

	[textTransformProperty.setNative](value: CoreTypes.TextTransformType) {
		this._update();
	}
}

export class TabView extends TabViewBase {
	public viewController: UITabBarControllerImpl;
	public items: TabViewItem[];

	private _delegate: UITabBarControllerDelegateImpl;
	private _moreNavigationControllerDelegate: UINavigationControllerDelegateImpl;
	private _iconsCache = {};
	private _bottomAccessoryNsView: View;
	private _ios: UITabBarControllerImpl;
	private _actionBarHiddenByTabView: boolean;

	constructor() {
		super();
		this.viewController = this._ios = UITabBarControllerImpl.initWithOwner(new WeakRef(this));
	}

	createNativeView() {
		// View controller can be disposed during view disposal, so make sure to create a new one if not defined
		if (!this._ios) {
			this.viewController = this._ios = UITabBarControllerImpl.initWithOwner(new WeakRef(this));
		}
		return this._ios.view;
	}

	initNativeView() {
		super.initNativeView();
		this._delegate = UITabBarControllerDelegateImpl.initWithOwner(new WeakRef(this));
		this._moreNavigationControllerDelegate = UINavigationControllerDelegateImpl.initWithOwner(new WeakRef(this));
	}

	disposeNativeView() {
		this._delegate = null;
		this._moreNavigationControllerDelegate = null;
		this.viewController = null;
		this._ios = null;
		super.disposeNativeView();
	}

	@profile
	public onLoaded() {
		super.onLoaded();

		const selectedIndex = this.selectedIndex;
		const selectedView = this.items && this.items[selectedIndex] && this.items[selectedIndex].view;
		if (selectedView instanceof Frame) {
			selectedView._pushInFrameStackRecursive();
		}

		if (this._ios) {
			this._ios.delegate = this._delegate;
		}

		// Re-apply bottom accessory if set
		if (this.iosBottomAccessory) {
			this._applyBottomAccessory(this.iosBottomAccessory, false);
		}
	}

	public onUnloaded() {
		if (this._ios) {
			this._ios.delegate = null;

			if (this._ios.moreNavigationController) {
				this._ios.moreNavigationController.delegate = null;
			}
		}
		// Avoid retaining custom view when unloading
		this._applyBottomAccessory(null, false);
		super.onUnloaded();
	}

	// @ts-ignore
	get ios(): UITabBarController {
		return this._ios;
	}

	public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
		//
	}

	public _setNativeViewFrame(nativeView: UIView, frame: CGRect) {
		//
	}

	public onSelectedIndexChanged(oldIndex: number, newIndex: number): void {
		const items = this.items;
		if (!items) {
			return;
		}

		const oldItem = items[oldIndex];
		if (oldItem) {
			oldItem.unloadView(oldItem.view);
		}

		const newItem = items[newIndex];
		if (newItem && this.isLoaded) {
			const selectedView = items[newIndex].view;
			if (selectedView instanceof Frame) {
				selectedView._pushInFrameStackRecursive();
			}

			newItem.loadView(newItem.view);
		}

		super.onSelectedIndexChanged(oldIndex, newIndex);
	}

	public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void {
		const width = layout.getMeasureSpecSize(widthMeasureSpec);
		const widthMode = layout.getMeasureSpecMode(widthMeasureSpec);

		const height = layout.getMeasureSpecSize(heightMeasureSpec);
		const heightMode = layout.getMeasureSpecMode(heightMeasureSpec);

		const widthAndState = View.resolveSizeAndState(width, width, widthMode, 0);
		const heightAndState = View.resolveSizeAndState(height, height, heightMode, 0);

		this.setMeasuredDimension(widthAndState, heightAndState);
	}

	public _onViewControllerShown(tabBarController: UITabBarController, viewController: UIViewController) {
		// This method could be called with the moreNavigationController or its list controller, so we have to check.
		if (Trace.isEnabled()) {
			Trace.write('TabView._onViewControllerShown(' + viewController + ');', Trace.categories.Debug);
		}
		if (tabBarController?.viewControllers && tabBarController.viewControllers.containsObject(viewController)) {
			this.selectedIndex = tabBarController.viewControllers.indexOfObject(viewController);
		} else {
			if (Trace.isEnabled()) {
				Trace.write('TabView._onViewControllerShown: viewController is not one of our viewControllers', Trace.categories.Debug);
			}
		}
	}

	public _handleTwoNavigationBars(backToMoreWillBeVisible: boolean) {
		if (Trace.isEnabled()) {
			Trace.write(`TabView._handleTwoNavigationBars(backToMoreWillBeVisible: ${backToMoreWillBeVisible})`, Trace.categories.Debug);
		}

		// The "< Back" and "< More" navigation bars should not be visible simultaneously.
		let page = this.page || this._selectedView?.page;

		if (!page && this._selectedView instanceof Frame) {
			page = this._selectedView.currentPage;
		}

		if (!page || !page.frame) {
			return;
		}

		const actionBarVisible = page.frame._getNavBarVisible(page);

		if (backToMoreWillBeVisible && actionBarVisible) {
			if (page.frame.ios) {
				page.frame.ios._disableNavBarAnimation = true;
				page.actionBarHidden = true;
				page.frame.ios._disableNavBarAnimation = false;
			} else {
				page.actionBarHidden = true;
			}

			this._actionBarHiddenByTabView = true;
			if (Trace.isEnabled()) {
				Trace.write(`TabView hid action bar`, Trace.categories.Debug);
			}

			return;
		}

		if (!backToMoreWillBeVisible && this._actionBarHiddenByTabView) {
			if (page.frame.ios) {
				page.frame.ios._disableNavBarAnimation = true;
				page.actionBarHidden = false;
				page.frame.ios._disableNavBarAnimation = false;
			} else {
				page.actionBarHidden = false;
			}

			this._actionBarHiddenByTabView = undefined;
			if (Trace.isEnabled()) {
				Trace.write(`TabView restored action bar`, Trace.categories.Debug);
			}

			return;
		}
	}

	private getViewController(item: TabViewItem): UIViewController {
		let newController: UIViewController = item.view ? item.view.viewController : null;

		if (newController) {
			item.setViewController(newController, newController.view);

			return newController;
		}

		if (item.view.ios instanceof UIViewController) {
			newController = item.view.ios;
			item.setViewController(newController, newController.view);
		} else if (item.view.ios && item.view.ios.controller instanceof UIViewController) {
			newController = item.view.ios.controller;
			item.setViewController(newController, newController.view);
		} else {
			newController = IOSHelper.UILayoutViewController.initWithOwner(new WeakRef(item.view)) as UIViewController;
			newController.view.addSubview(item.view.nativeViewProtected);
			item.view.viewController = newController;
			item.setViewController(newController, item.view.nativeViewProtected);
		}

		return newController;
	}

	private setViewControllers(items: TabViewItem[]) {
		const length = items ? items.length : 0;
		if (length === 0) {
			if (SDK_VERSION >= 18) {
				// Clear tabs on iOS 18+ when there are no items.
				try {
					this._ios.tabs = NSArray.arrayWithArray([]);
				} catch (e) {
					// Fallback if tabs API is unavailable for some reason.
					this._ios.viewControllers = null;
				}
			} else {
				this._ios.viewControllers = null;
			}
			return;
		}

		if (SDK_VERSION >= 18) {
			// iOS 18+: build UITab instances and assign them to the controller.
			const tabs = [];
			const controllers = [];
			items.forEach((item, i) => {
				const controller = this.getViewController(item);
				controllers.push(controller);
				const icon = this._getIcon(item);
				const title = item.title || '';
				const identifier = `${i}`;
				let tab: UITab;
				if (item.role === 'search') {
					tab = UISearchTab.alloc().initWithTitleImageIdentifierViewControllerProvider(title, icon, identifier, (t) => {
						return controller;
					});
				} else {
					tab = UITab.alloc().initWithTitleImageIdentifierViewControllerProvider(title, icon, identifier, (t) => {
						return controller;
					});
				}

				tabs.push(tab);
				(<TabViewItemDefinition>item).canBeLoaded = true;
			});

			try {
				// Prefer animated setter when available.
				this._ios.tabs = NSArray.arrayWithArray(tabs);
			} catch (e) {}
			this._ios.viewControllers = NSArray.arrayWithArray(controllers);
			this._ios.customizableViewControllers = null;
		} else {
			// iOS < 18: keep using UITabBarItem-based configuration.
			const controllers = [];
			const states = getTitleAttributesForStates(this);

			items.forEach((item, i) => {
				const controller = this.getViewController(item);
				const icon = this._getIcon(item);
				const tabBarItem = UITabBarItem.alloc().initWithTitleImageTag(item.title || '', icon, i);
				updateTitleAndIconPositions(item, tabBarItem, controller);

				if (!__VISIONOS__ && SDK_VERSION < 15) {
					applyStatesToItem(tabBarItem, states);
				}

				controller.tabBarItem = tabBarItem;
				controllers.push(controller);
				(<TabViewItemDefinition>item).canBeLoaded = true;
			});

			if (SDK_VERSION >= 15) {
				this.updateBarItemAppearance(<UITabBar>this._ios.tabBar, states);
			}

			this._ios.viewControllers = NSArray.arrayWithArray(controllers);
			this._ios.customizableViewControllers = null;
		}

		// When we set this._ios.viewControllers, someone is clearing the moreNavigationController.delegate, so we have to reassign it each time here.
		this._ios.moreNavigationController.delegate = this._moreNavigationControllerDelegate;
	}

	private _getIconRenderingMode(): UIImageRenderingMode {
		switch (this.iosIconRenderingMode) {
			case 'alwaysOriginal':
				return UIImageRenderingMode.AlwaysOriginal;
			case 'alwaysTemplate':
				return UIImageRenderingMode.AlwaysTemplate;
			case 'automatic':
			default:
				return UIImageRenderingMode.Automatic;
		}
	}

	public _getIcon(item: TabViewItem): UIImage {
		if (!item || !item.iconSource) {
			return null;
		}

		let image: UIImage = this._iconsCache[item.iconSource];
		if (!image) {
			let is: ImageSource;
			if (isSystemURI(item.iconSource)) {
				is = ImageSource.fromSystemImageSync(item.iconSource.slice(SYSTEM_PREFIX.length));
			} else if (isFontIconURI(item.iconSource)) {
				// Allow specifying a separate font family for the icon via style.iconFontFamily.
				// If provided, construct a Font from the family and (optionally) size from fontInternal.
				let iconFont = item.style.fontInternal;
				const iconFontFamily = item.iconFontFamily || item.style.iconFontFamily;
				if (iconFontFamily) {
					// Preserve size/style from existing fontInternal if present.
					const baseFont = item.style.fontInternal || Font.default;
					iconFont = baseFont.withFontFamily(iconFontFamily);
				}
				is = ImageSource.fromFontIconCodeSync(item.iconSource.slice(FONT_PREFIX.length), iconFont, item.style.color);
			} else {
				is = ImageSource.fromFileOrResourceSync(item.iconSource);
			}
			if (is && is.ios) {
				const originalRenderedImage = is.ios.imageWithRenderingMode(this._getIconRenderingMode());
				this._iconsCache[item.iconSource] = originalRenderedImage;
				image = originalRenderedImage;
			} else {
				traceMissingIcon(item.iconSource);
			}
		}

		return image;
	}

	private _updateIOSTabBarColorsAndFonts(): void {
		if (!this.items) {
			return;
		}

		const tabBar = <UITabBar>this.ios.tabBar;
		const states = getTitleAttributesForStates(this);
		if (SDK_VERSION >= 15) {
			this.updateBarItemAppearance(tabBar, states);
		} else {
			for (let i = 0; i < tabBar.items.count; i++) {
				applyStatesToItem(tabBar.items[i], states);
			}
		}
	}

	private updateBarItemAppearance(tabBar: UITabBar, states: TabStates) {
		const appearance = this._getAppearance(tabBar);
		const itemAppearances = ['stackedLayoutAppearance', 'inlineLayoutAppearance', 'compactInlineLayoutAppearance'];
		for (const itemAppearance of itemAppearances) {
			appearance[itemAppearance].normal.titleTextAttributes = states.normalState;
			appearance[itemAppearance].selected.titleTextAttributes = states.selectedState;
		}
		this._updateAppearance(tabBar, appearance);
	}

	private _getAppearance(tabBar: UITabBar) {
		if (tabBar.standardAppearance == null) {
			const appearance = UITabBarAppearance.new();
			appearance.stackedLayoutAppearance = appearance.inlineLayoutAppearance = appearance.compactInlineLayoutAppearance = UITabBarItemAppearance.new();
			return appearance;
		}
		return tabBar.standardAppearance;
	}

	private _updateAppearance(tabBar: UITabBar, appearance: UITabBarAppearance) {
		tabBar.standardAppearance = appearance;
		if (SDK_VERSION >= 15) {
			tabBar.scrollEdgeAppearance = appearance;
		}
	}

	[selectedIndexProperty.setNative](value: number) {
		if (Trace.isEnabled()) {
			Trace.write('TabView._onSelectedIndexPropertyChangedSetNativeValue(' + value + ')', Trace.categories.Debug);
		}

		if (value > -1) {
			this._ios.selectedIndex = value;
		}
	}

	[itemsProperty.getDefault](): TabViewItem[] {
		return null;
	}
	[itemsProperty.setNative](value: TabViewItem[]) {
		this.setViewControllers(value);
		selectedIndexProperty.coerce(this);
	}

	[tabTextFontSizeProperty.getDefault](): number {
		return null;
	}
	[tabTextFontSizeProperty.setNative](value: number | { nativeSize: number }) {
		this._updateIOSTabBarColorsAndFonts();
	}

	[tabTextColorProperty.getDefault](): UIColor {
		return null;
	}
	[tabTextColorProperty.setNative](value: UIColor | Color) {
		this._updateIOSTabBarColorsAndFonts();
	}

	[tabBackgroundColorProperty.getDefault](): UIColor {
		return this._ios.tabBar.barTintColor;
	}
	[tabBackgroundColorProperty.setNative](value: UIColor | Color) {
		if (SDK_VERSION >= 13) {
			const appearance = this._getAppearance(this._ios.tabBar);
			appearance.configureWithDefaultBackground();
			appearance.backgroundColor = value instanceof Color ? value.ios : value;
			this._updateAppearance(this._ios.tabBar, appearance);
		} else {
			this._ios.tabBar.barTintColor = value instanceof Color ? value.ios : value;
		}
	}

	[selectedTabTextColorProperty.getDefault](): UIColor {
		return this._ios.tabBar.tintColor;
	}
	[selectedTabTextColorProperty.setNative](value: UIColor) {
		this._ios.tabBar.tintColor = value instanceof Color ? value.ios : value;
		this._updateIOSTabBarColorsAndFonts();
	}

	// TODO: Move this to TabViewItem
	[fontInternalProperty.getDefault](): Font {
		return null;
	}
	[fontInternalProperty.setNative](value: Font) {
		this._updateIOSTabBarColorsAndFonts();
	}

	// TODO: Move this to TabViewItem
	[iosIconRenderingModeProperty.getDefault](): 'automatic' | 'alwaysOriginal' | 'alwaysTemplate' {
		return 'automatic';
	}
	[iosIconRenderingModeProperty.setNative](value: 'automatic' | 'alwaysOriginal' | 'alwaysTemplate') {
		this._iconsCache = {};
		const items = this.items;
		if (items && items.length) {
			for (let i = 0, length = items.length; i < length; i++) {
				const item = items[i];
				if (item.iconSource) {
					(item as TabViewItem)._update();
				}
			}
		}
	}

	// iOS 26+: bottom accessory support
	[iosBottomAccessoryProperty.getDefault](): View {
		return null;
	}
	[iosBottomAccessoryProperty.setNative](value: View) {
		this._applyBottomAccessory(value, false);
	}

	// iOS 26+: tab bar minimize behavior
	[iosTabBarMinimizeBehaviorProperty.getDefault](): 'automatic' | 'never' | 'onScrollDown' | 'onScrollUp' {
		return 'automatic';
	}
	[iosTabBarMinimizeBehaviorProperty.setNative](value: 'automatic' | 'never' | 'onScrollDown' | 'onScrollUp') {
		if (SDK_VERSION < 26) {
			return;
		}
		let mapped: UITabBarMinimizeBehavior;
		switch (value) {
			case 'never':
				mapped = UITabBarMinimizeBehavior.Never;
				break;
			case 'onScrollDown':
				mapped = UITabBarMinimizeBehavior.OnScrollDown;
				break;
			case 'onScrollUp':
				mapped = UITabBarMinimizeBehavior.OnScrollUp;
				break;
			case 'automatic':
			default:
				mapped = UITabBarMinimizeBehavior.Automatic;
		}
		this._ios.tabBarMinimizeBehavior = mapped;
	}

	private _applyBottomAccessory(value: View | null, animated: boolean) {
		// Guard for platform availability
		if (SDK_VERSION < 26) {
			return;
		}

		const setAccessory = (accessory: UITabAccessory | null) => {
			try {
				this._ios.setBottomAccessoryAnimated(accessory, animated);
			} catch (err) {
				// Fallback to property if needed
				this._ios.bottomAccessory = accessory;
			}
		};

		// Clear previous
		if (!value) {
			// Clear on controller
			setAccessory(null);
			// Tear down previously managed NS view
			if (this._bottomAccessoryNsView) {
				// Do not remove from a parent; we didn't add it to the NS view tree.
				try {
					this._bottomAccessoryNsView._tearDownUI(true);
				} catch (_) {}
				this._bottomAccessoryNsView = null;
			}
			return;
		}

		// Ensure the NativeScript view has a native view
		const nsView = value;
		if (!nsView.nativeViewProtected) {
			// mirror dialogs approach to setup UI for a detached view
			nsView._setupUI({} as any);
		}
		// Just mark it loaded, if not already, so measurement & styling are applied.
		if (!nsView.isLoaded) {
			// In detached scenarios we simply callLoaded after setup.
			nsView.callLoaded();
		}
		const contentView = nsView.nativeViewProtected as UIView;
		if (!contentView) {
			return;
		}

		// Use frame-based sizing; keep autoresizing mask-based behavior enabled (no Auto Layout constraints added here).
		contentView.translatesAutoresizingMaskIntoConstraints = true;
		// Measure desired height with the tab bar width
		let tabBarWidth = this._ios?.tabBar?.frame?.size?.width || Screen.mainScreen.screen.bounds.size.width;
		// Account for safe area insets so accessory doesn't extend visually past rounded corners
		if (this._ios?.tabBar?.safeAreaInsets) {
			const insets = this._ios.tabBar.safeAreaInsets;
			// Reduce usable width by left+right safe area (typically 0, but defensive)
			const horizontalInsets = insets.left + insets.right;
			if (horizontalInsets > 0 && horizontalInsets < tabBarWidth) {
				tabBarWidth -= horizontalInsets;
			}
		}
		const tabBarWidthPx = layout.toDevicePixels(tabBarWidth);
		// Prefer flooring to avoid overshooting container by +1px due to FP rounding
		const tabBarWidthPxRounded = Math.floor(tabBarWidthPx);
		let measuredHeight = 0;
		// Measure using device-pixel width; flooring prevents +1px expansion
		const widthSpec = layout.makeMeasureSpec(tabBarWidthPxRounded, layout.EXACTLY);
		const heightSpec = layout.makeMeasureSpec(0, layout.UNSPECIFIED);
		nsView.measure(widthSpec, heightSpec);
		measuredHeight = layout.toDeviceIndependentPixels(nsView.getMeasuredHeight());

		// Use a sensible minimum height (44pt button row) if measurement is tiny
		const minHeight = 44;
		const finalHeight = Math.max(minHeight, measuredHeight || 0);
		// Rely on container height constraint (below) and frame-based layout inside container.
		const container = NSTabAccessoryContainer.initWithOwner(new WeakRef(nsView));
		container.translatesAutoresizingMaskIntoConstraints = true;
		container.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
		// Mask any subpixel spill just in case
		container.clipsToBounds = true;
		container.addSubview(contentView);
		// Constrain the container height (not the content) so UIKit has a concrete size.
		const containerHeight = container.heightAnchor.constraintEqualToConstant(finalHeight);
		containerHeight.priority = 999;
		NSLayoutConstraint.activateConstraints([containerHeight]);

		const accessory = UITabAccessory.alloc().initWithContentView(container);
		setAccessory(accessory);
		// Work around UIKit occasionally caching accessory sizes too aggressively
		// by explicitly triggering a layout pass on the tab bar.
		const tabBar = this._ios?.tabBar;
		if (tabBar) {
			tabBar.setNeedsLayout();
			tabBar.layoutIfNeeded();
		}
		// Keep references for later teardown
		this._bottomAccessoryNsView = nsView;
	}
}

@NativeClass
class NSTabAccessoryContainer extends UIView {
	_owner: WeakRef<View>;
	static initWithOwner(owner: WeakRef<View>): NSTabAccessoryContainer {
		const v = NSTabAccessoryContainer.new() as NSTabAccessoryContainer;
		v._owner = owner;
		return v;
	}

	override traitCollectionDidChange(previousTraitCollection: UITraitCollection) {
		super.traitCollectionDidChange(previousTraitCollection);
		if (!previousTraitCollection) {
			return;
		}
		// When size classes change (e.g., compact  regular),
		// ask UIKit to recompute this accessory's intrinsic size.
		if (this.traitCollection?.horizontalSizeClass !== previousTraitCollection.horizontalSizeClass) {
			this.invalidateIntrinsicContentSize();
			this.setNeedsLayout();
			this.layoutIfNeeded();
		}
	}

	override layoutSubviews() {
		super.layoutSubviews();
		const owner = this._owner?.deref();
		if (!owner?.nativeViewProtected) return;
		owner.nativeViewProtected.frame = this.bounds;
		const w = this.bounds.size.width;
		const h = this.bounds.size.height;
		try {
			// Convert to device pixels and floor to avoid +1px overshoot from rounding
			let wp = Math.floor(layout.toDevicePixels(w));
			const hp = Math.floor(layout.toDevicePixels(h));
			// Clamp width to <= container pixel width (defensive)
			const containerPxWidth = Math.floor(layout.toDevicePixels(this.bounds.size.width));
			if (wp > containerPxWidth) {
				wp = containerPxWidth;
			}
			// Ensure NS view and its children are measured with the final container width
			const widthSpec = layout.makeMeasureSpec(wp, layout.EXACTLY);
			const heightSpec = layout.makeMeasureSpec(hp, layout.EXACTLY);
			owner.measure(widthSpec, heightSpec);
			owner.layout(0, 0, wp, hp);
		} catch (_) {}
	}
}

interface TabStates {
	normalState?: any;
	selectedState?: any;
}

function getTitleAttributesForStates(tabView: TabView): TabStates {
	const result: TabStates = {
		normalState: NSMutableDictionary.new(),
		selectedState: NSMutableDictionary.new(),
	};

	const titleFontSize = tabView.style.tabTextFontSize;
	let font = tabView.style.fontInternal || Font.default;
	if (titleFontSize != null) {
		font = font.withFontSize(titleFontSize);
	}

	const nativeFont: UIFont = font.getUIFont(UIFont.systemFontOfSize(UIFont.labelFontSize));
	result.normalState.setValueForKey(nativeFont, NSFontAttributeName);
	result.selectedState.setValueForKey(nativeFont, NSFontAttributeName);

	const titleColor = tabView.style.tabTextColor;
	if (titleColor instanceof Color) {
		result.normalState.setValueForKey(titleColor.ios, UITextAttributeTextColor);
	}
	const selectedTitleColor = tabView.style.selectedTabTextColor;
	if (selectedTitleColor instanceof Color) {
		result.selectedState.setValueForKey(selectedTitleColor.ios, UITextAttributeTextColor);
	}

	return result;
}

function applyStatesToItem(item: UITabBarItem, states: TabStates) {
	item.setTitleTextAttributesForState(states.normalState, UIControlState.Normal);
	item.setTitleTextAttributesForState(states.selectedState, UIControlState.Selected);
}
